home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / pluginy Firefox / 4838 / 4838.xpi / chrome / multipletab.jar / content / multipletab / multipletab.js < prev    next >
Text File  |  2010-02-03  |  93KB  |  3,150 lines

  1. var MultipleTabService = { 
  2.     PREFROOT : 'extensions.multipletab@piro.sakura.ne.jp',
  3.  
  4.     tabDragMode : -1,
  5.     TAB_DRAG_MODE_DEFAULT : 0,
  6.     TAB_DRAG_MODE_SELECT  : 1,
  7.     TAB_DRAG_MODE_SWITCH  : 2,
  8.  
  9.     tabAccelClickMode : -1,
  10.     tabShiftClickMode : -1,
  11.     TAB_CLICK_MODE_DEFAULT : 0,
  12.     TAB_CLICK_MODE_SELECT  : 1,
  13.  
  14.     kSELECTION_STYLE : 'multipletab-selection-style',
  15.     kSELECTED        : 'multipletab-selected',
  16.     kSELECTED_DUPLICATING : 'multipletab-selected-duplicating',
  17.     kREADY_TO_CLOSE  : 'multipletab-ready-to-close',
  18.     kINSERT_AFTER    : 'multipletab-insertafter',
  19.     kINSERT_BEFORE   : 'multipletab-insertbefore',
  20.     kAVAILABLE       : 'multipletab-available',
  21.     kENABLED         : 'multipletab-enabled',
  22.  
  23.     kSELECTION_MENU        : 'multipletab-selection-menu',
  24.     kCONTEXT_MENU_TEMPLATE : 'multipletab-tabcontext-menu-template',
  25.  
  26.     kCUSTOM_TYPE_OFFSET    : 1000,
  27.     formats          : [],
  28.     formatsTimeStamp : -1,
  29.  
  30.     selectableItems : [
  31.         { name : 'clipboard',
  32.           key  : 'extensions.multipletab.clipboard.formatType' },
  33.         { name : 'clipboardAll',
  34.           key  : 'extensions.multipletab.clipboard.formatType' },
  35.         { name : 'saveTabs',
  36.           key  : 'extensions.multipletab.saveTabs.saveType' }
  37.     ],
  38.  
  39.     lineFeed : '\r\n',
  40.     
  41. /* Utilities */ 
  42.     
  43.     NSResolver : { 
  44.         lookupNamespaceURI : function MTS_lookupNamespaceURI(aPrefix)
  45.         {
  46.             switch (aPrefix)
  47.             {
  48.                 case 'xul':
  49.                     return 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul';
  50.                 case 'html':
  51.                 case 'xhtml':
  52.                     return 'http://www.w3.org/1999/xhtml';
  53.                 case 'xlink':
  54.                     return 'http://www.w3.org/1999/xlink';
  55.                 default:
  56.                     return '';
  57.             }
  58.         }
  59.     },
  60.     evaluateXPath : function MTS_evaluateXPath(aExpression, aContext, aType)
  61.     {
  62.         if (!aType) aType = XPathResult.ORDERED_NODE_SNAPSHOT_TYPE;
  63.         try {
  64.             var doc = aContext.ownerDocument || aContext || document;
  65.             var xpathResult = doc.evaluate(
  66.                     aExpression,
  67.                     aContext || document,
  68.                     this.NSResolver,
  69.                     aType,
  70.                     null
  71.                 );
  72.         }
  73.         catch(e) {
  74.             return {
  75.                 singleNodeValue : null,
  76.                 snapshotLength  : 0,
  77.                 snapshotItem    : function MTS_snapshotItem() {
  78.                     return null
  79.                 }
  80.             };
  81.         }
  82.         return xpathResult;
  83.     },
  84.     
  85.     getArrayFromXPathResult : function MTS_getArrayFromXPathResult(aXPathResult) 
  86.     {
  87.         if (!(aXPathResult instanceof Components.interfaces.nsIDOMXPathResult)) {
  88.             aXPathResult = this.evaluateXPath.apply(this, arguments);
  89.         }
  90.         var max = aXPathResult.snapshotLength;
  91.         var array = new Array(max);
  92.         if (!max) return array;
  93.  
  94.         for (var i = 0; i < max; i++)
  95.         {
  96.             array[i] = aXPathResult.snapshotItem(i);
  97.         }
  98.  
  99.         return array;
  100.     },
  101.   
  102.     evalInSandbox : function MTS_evalInSandbox(aCode, aOwner) 
  103.     {
  104.         try {
  105.             var sandbox = new Components.utils.Sandbox(aOwner || 'about:blank');
  106.             return Components.utils.evalInSandbox(aCode, sandbox);
  107.         }
  108.         catch(e) {
  109.         }
  110.         return void(0);
  111.     },
  112.  
  113. // XPConnect 
  114.     
  115.     get SessionStore() { 
  116.         if (!this._SessionStore) {
  117.             this._SessionStore = Components.classes['@mozilla.org/browser/sessionstore;1'].getService(Components.interfaces.nsISessionStore);
  118.         }
  119.         return this._SessionStore;
  120.     },
  121.     _SessionStore : null,
  122.  
  123.     get IOService() 
  124.     {
  125.         if (!this._IOService) {
  126.             this._IOService = Components
  127.                     .classes['@mozilla.org/network/io-service;1']
  128.                     .getService(Components.interfaces.nsIIOService);
  129.         }
  130.         return this._IOService;
  131.     },
  132.     _IOService : null,
  133.  
  134.     get PromptService() 
  135.     {
  136.         if (!this._PromptService) {
  137.             this._PromptService = Components
  138.                     .classes['@mozilla.org/embedcomp/prompt-service;1']
  139.                     .getService(Components.interfaces.nsIPromptService);
  140.         }
  141.         return this._PromptService;
  142.     },
  143.     _PromptService : null,
  144.  
  145.     get EffectiveTLD() 
  146.     {
  147.         if (!('_EffectiveTLD' in this)) {
  148.             this._EffectiveTLD = 'nsIEffectiveTLDService' in Components.interfaces ?
  149.                 Components
  150.                     .classes['@mozilla.org/network/effective-tld-service;1']
  151.                     .getService(Components.interfaces.nsIEffectiveTLDService) :
  152.                 null ;
  153.         }
  154.         return this._EffectiveTLD;
  155.     },
  156. //    _EffectiveTLD : null,
  157.   
  158.     isDisabled : function MTS_isDisabled() 
  159.     {
  160.         return (document.getElementById('cmd_CustomizeToolbars').getAttribute('disabled') == 'true');
  161.     },
  162.  
  163.     get allowMoveMultipleTabs() 
  164.     {
  165.         return this.getPref('extensions.multipletab.tabdrag.moveMultipleTabs');
  166.     },
  167.  
  168.     get browser() 
  169.     {
  170.         return gBrowser;
  171.     },
  172.  
  173.     get bundle() { 
  174.         if (!this._bundle) {
  175.             this._bundle = document.getElementById('multipletab-bundle');
  176.         }
  177.         return this._bundle;
  178.     },
  179.     _bundle : null,
  180.  
  181.     get tabbrowserBundle() { 
  182.         if (!this._tabbrowserBundle) {
  183.             this._tabbrowserBundle = document.getElementById('multipletab-tabbrowserBundle');
  184.         }
  185.         return this._tabbrowserBundle;
  186.     },
  187.     _tabbrowserBundle : null,
  188.  
  189. // tabs 
  190.     
  191.     warnAboutClosingTabs : function MTS_warnAboutClosingTabs(aTabsCount) 
  192.     {
  193.         if (
  194.             aTabsCount <= 1 ||
  195.             !this.getPref('browser.tabs.warnOnClose')
  196.             )
  197.             return true;
  198.         var checked = { value:true };
  199.         window.focus();
  200.         var shouldClose = this.PromptService.confirmEx(window,
  201.                 this.tabbrowserBundle.getString('tabs.closeWarningTitle'),
  202.                 this.tabbrowserBundle.getFormattedString('tabs.closeWarningMultipleTabs', [aTabsCount]),
  203.                 (this.PromptService.BUTTON_TITLE_IS_STRING * this.PromptService.BUTTON_POS_0) +
  204.                 (this.PromptService.BUTTON_TITLE_CANCEL * this.PromptService.BUTTON_POS_1),
  205.                 this.tabbrowserBundle.getString('tabs.closeButtonMultiple'),
  206.                 null, null,
  207.                 this.tabbrowserBundle.getString('tabs.closeWarningPromptMe'),
  208.                 checked
  209.             ) == 0;
  210.         if (shouldClose && !checked.value)
  211.             this.setPref('browser.tabs.warnOnClose', false);
  212.         return shouldClose;
  213.     },
  214.  
  215.     getIndexesFromTabs : function MTS_getIndexesFromTabs(aTabs) 
  216.     {
  217.         return Array.slice(aTabs)
  218.                 .map(function(aTab) {
  219.                     return aTab._tPos;
  220.                 })
  221.                 .sort();
  222.     },
  223.  
  224.     sortTabs : function MTS_sortTabs(aTabs) 
  225.     {
  226.         return Array.slice(aTabs)
  227.                 .sort(function(aA, aB) {
  228.                     return aA._tPos - aB._tPos;
  229.                 });
  230.     },
  231.  
  232.     getSelectedTabs : function MTS_getSelectedTabs(aTabBrowser) 
  233.     {
  234.         return this.getArrayFromXPathResult(
  235.                 'descendant::xul:tab[@'+this.kSELECTED+'="true"]',
  236.                 (aTabBrowser || this.browser).mTabContainer
  237.             );
  238.     },
  239.  
  240.     getReadyToCloseTabs : function MTS_getReadyToCloseTabs(aTabBrowser) 
  241.     {
  242.         return this.getArrayFromXPathResult(
  243.                 'descendant::xul:tab[@'+this.kREADY_TO_CLOSE+'="true"]',
  244.                 (aTabBrowser || this.browser).mTabContainer
  245.             );
  246.     },
  247.  
  248.     getLeftTabsOf : function MTS_getLeftTabsOf(aTab) 
  249.     {
  250.         return this.getArrayFromXPathResult(
  251.                 'preceding-sibling::xul:tab',
  252.                 aTab
  253.             );
  254.     },
  255.  
  256.     getRightTabsOf : function MTS_getRightTabsOf(aTab) 
  257.     {
  258.         return this.getArrayFromXPathResult(
  259.                 'following-sibling::xul:tab',
  260.                 aTab
  261.             );
  262.     },
  263.  
  264.     getSimilarTabsOf : function MTS_getSimilarTabsOf(aCurrentTab, aTabs) 
  265.     {
  266.         var resultTabs = [];
  267.         if (!aCurrentTab) return resultTabs;
  268.  
  269.         if (!aTabs)
  270.             aTabs = this.getTabsArray(this.getTabBrowserFromChild(aCurrentTab));
  271.  
  272.         try {
  273.             var currentDomain = this.getDomainFromURI(aCurrentTab.linkedBrowser.currentURI);
  274.         }
  275.         catch(e) {
  276.             return resultTabs;
  277.         }
  278.  
  279.         Array.slice(aTabs).forEach(function(aTab) {
  280.             if (aTab == aCurrentTab) return;
  281.             if (this.getDomainFromURI(aTab.linkedBrowser.currentURI) == currentDomain)
  282.                 resultTabs.push(aTab);
  283.         }, this);
  284.         return resultTabs;
  285.     },
  286.     getDomainFromURI : function MTS_getDomainFromURI(aURI)
  287.     {
  288.         if (!aURI) return null;
  289.         try {
  290.             if (!(aURI instanceof Ci.nsIURI)) aURI = this.makeURIFromSpec(aURI);
  291.         }
  292.         catch(e) {
  293.             return null;
  294.         }
  295.         if (this.getPref('extensions.multipletab.useEffectiveTLD') && this.EffectiveTLD) {
  296.             try {
  297.                 var domain = this.EffectiveTLD.getBaseDomain(aURI, 0);
  298.                 if (domain) return domain;
  299.             }
  300.             catch(e) {
  301.             }
  302.         }
  303.         try {
  304.             var host = aURI.host;
  305.             return host;
  306.         }
  307.         catch(e) {
  308.         }
  309.         return null;
  310.     },
  311.     makeURIFromSpec : function MTS_makeURIFromSpec(aURI)
  312.     {
  313.         var newURI;
  314.         aURI = aURI || '';
  315.         if (aURI && String(aURI).indexOf('file:') == 0) {
  316.             var fileHandler = this.IOService
  317.                         .getProtocolHandler('file')
  318.                         .QueryInterface(Components.interfaces.nsIFileProtocolHandler);
  319.             var tempLocalFile = fileHandler.getFileFromURLSpec(aURI);
  320.             newURI = this.IOService.newFileURI(tempLocalFile);
  321.         }
  322.         else {
  323.             newURI = this.IOService.newURI(aURI, null, null);
  324.         }
  325.         return newURI;
  326.     },
  327.  
  328.     getTabFromEvent : function MTS_getTabFromEvent(aEvent, aReallyOnTab) 
  329.     {
  330.         var tab = this.evaluateXPath(
  331.                 'ancestor-or-self::xul:tab[ancestor::xul:tabbrowser]',
  332.                 aEvent.originalTarget || aEvent.target,
  333.                 XPathResult.FIRST_ORDERED_NODE_TYPE
  334.             ).singleNodeValue;
  335.         if (tab || aReallyOnTab) return tab;
  336.  
  337.         var b = this.getTabBrowserFromChild(aEvent.originalTarget);
  338.         if (b &&
  339.             'treeStyleTab' in b &&
  340.             'getTabFromTabbarEvent' in b.treeStyleTab) { // Tree Style Tab
  341.             return b.treeStyleTab.getTabFromTabbarEvent(aEvent);
  342.         }
  343.         return null;
  344.     },
  345.  
  346.     getTabFromChild : function MTS_getTabFromChild(aNode) 
  347.     {
  348.         return this.evaluateXPath(
  349.                 'ancestor-or-self::xul:tab[ancestor::xul:tabbrowser]',
  350.                 aNode,
  351.                 XPathResult.FIRST_ORDERED_NODE_TYPE
  352.             ).singleNodeValue;
  353.     },
  354.  
  355.     getTabBrowserFromChild : function MTS_getTabBrowserFromChild(aTab) 
  356.     {
  357.         return this.evaluateXPath(
  358.                 'ancestor-or-self::xul:tabbrowser',
  359.                 aTab,
  360.                 XPathResult.FIRST_ORDERED_NODE_TYPE
  361.             ).singleNodeValue;
  362.     },
  363.  
  364.     getTabs : function MTS_getTabs(aTabBrowser) 
  365.     {
  366.         return this.evaluateXPath(
  367.                 'descendant::xul:tab',
  368.                 aTabBrowser.mTabContainer
  369.             );
  370.     },
  371.  
  372.     getTabsArray : function MTS_getTabsArray(aTabBrowser) 
  373.     {
  374.         return this.getArrayFromXPathResult(this.getTabs(aTabBrowser));
  375.     },
  376.  
  377.     getTabAt : function MTS_getTabAt(aIndex, aTabBrowser) 
  378.     {
  379.         if (aIndex < 0) return null;
  380.         return this.evaluateXPath(
  381.                 'descendant::xul:tab['+(aIndex+1)+']',
  382.                 aTabBrowser.mTabContainer,
  383.                 XPathResult.FIRST_ORDERED_NODE_TYPE
  384.             ).singleNodeValue;
  385.     },
  386.  
  387.     getNextTab : function MTS_getNextTab(aTab) 
  388.     {
  389.         return this.evaluateXPath(
  390.                 'following-sibling::xul:tab[1]',
  391.                 aTab,
  392.                 XPathResult.FIRST_ORDERED_NODE_TYPE
  393.             ).singleNodeValue;
  394.     },
  395.  
  396.     getPreviousTab : function MTS_getPreviousTab(aTab) 
  397.     {
  398.         return this.evaluateXPath(
  399.                 'preceding-sibling::xul:tab[1]',
  400.                 aTab,
  401.                 XPathResult.FIRST_ORDERED_NODE_TYPE
  402.             ).singleNodeValue;
  403.     },
  404.     
  405.     // old method (for backward compatibility) 
  406.     getTabBrowserFromChildren : function MTS_getTabBrowserFromChildren(aTab)
  407.     {
  408.         return this.getTabBrowserFromChild(aTab);
  409.     },
  410.   
  411.     filterBlankTabs : function MTS_filterBlankTabs(aTabs) 
  412.     {
  413.         return aTabs.filter(function(aTab) {
  414.                 return aTab.linkedBrowser.currentURI.spec != 'about:blank';
  415.             });
  416.     },
  417.  
  418.     makeTabBlank : function MTS_makeTabBlank(aTab) 
  419.     {
  420.         var b = aTab.linkedBrowser;
  421.         try {
  422.             b.stop();
  423.             if (b.sessionHistory)
  424.                 b.sessionHistory.PurgeHistory(b.sessionHistory.count);
  425.         }
  426.         catch(e) {
  427.             dump(e+'\n');
  428.         }
  429.         if (b.contentWindow && b.contentWindow.location)
  430.             b.contentWindow.location.replace('about:blank');
  431.  
  432.         delete aTab.linkedBrowser.__SS_data; // Firefox 3.6-
  433.         delete aTab.linkedBrowser.parentNode.__SS_data; // -Firefox 3.5
  434.         delete aTab.__SS_extdata;
  435.     },
  436.  
  437.     irrevocableRemoveTab : function MTS_irrevocableRemoveTab(aTab, aTabBrowser) 
  438.     {
  439.         // nsSessionStore.js doesn't save the tab to the undo cache
  440.         // if the tab is completely blank.
  441.         this.makeTabBlank(aTab);
  442.  
  443.         // override session data to prevent undo
  444.         var data = {
  445.                 entries : [],
  446.                 _tabStillLoading : true, // for Firefox 3.5 or later
  447.                 _tab : aTab // for Firefox 3.0.x
  448.             };
  449.         aTab.linkedBrowser.__SS_data = data; // Firefox 3.6-
  450.         aTab.linkedBrowser.parentNode.__SS_data = data; // -Firefox 3.5
  451.  
  452.         (aTabBrowser || this.getTabBrowserFromChild(aTab))
  453.             .removeTab(aTab);
  454.     },
  455.  
  456.     ensureLoaded : function MTS_ensureLoaded(aTab) 
  457.     {
  458.         // for BarTap ( https://addons.mozilla.org/firefox/addon/67651 )
  459.         if (aTab.getAttribute('ontap') == 'true') {
  460.             var event = document.createEvent('Event');
  461.             event.initEvent('BarTapLoad', true, true);
  462.             aTab.linkedBrowser.dispatchEvent(event);
  463.             return true;
  464.         }
  465.         return false;
  466.     },
  467.   
  468. // bundled tabs 
  469.     
  470.     getBundledTabsOf : function MTS_getBundledTabsOf(aTab, aInfo) 
  471.     {
  472.         if (!aInfo) aInfo = {};
  473.         aInfo.sourceWindow = null;
  474.         aInfo.sourceBrowser = null;
  475.         var tabs = [];
  476.  
  477.         var w, b;
  478.         if (
  479.             !aTab ||
  480.             aTab.localName != 'tab' ||
  481.             !(w = aTab.ownerDocument.defaultView) ||
  482.             !('MultipleTabService' in w) ||
  483.             !(b = w.MultipleTabService.getTabBrowserFromChild(aTab))
  484.             )
  485.             return tabs;
  486.  
  487.         aInfo.sourceWindow = w;
  488.         aInfo.sourceBrowser = b;
  489.         return w.MultipleTabService.getSelectedTabs(b);
  490.     },
  491.  
  492.     rearrangeBundledTabsOf : function MTS_rearrangeBundledTabsOf() 
  493.     {
  494.         var baseTab,
  495.             oldBasePosition = -1,
  496.             tabs;
  497.         Array.slice(arguments).forEach(function(aArg) {
  498.             if (aArg instanceof Components.interfaces.nsIDOMNode)
  499.                 baseTab = aArg;
  500.             else if (typeof aArg == 'number')
  501.                 oldBasePosition = aArg;
  502.             else if (typeof aArg == 'object')
  503.                 tabs = aArg;
  504.         });
  505.  
  506.         var b       = this.getTabBrowserFromChild(baseTab);
  507.         var allTabs = this.getTabsArray(b);
  508.         if (!tabs || !tabs.length)
  509.             tabs = this.getSelectedTabs(b);
  510.  
  511.         var otherTabs = tabs.filter(function(aTab) {
  512.                 return aTab != baseTab;
  513.             });
  514.  
  515.         // step 1: calculate old positions of all tabs
  516.         var oldTabs = allTabs.slice(0);
  517.         if (oldBasePosition < 0) {
  518.             let positionInTabs = tabs.indexOf(baseTab);
  519.             if (positionInTabs < 0 || !tabs.length)
  520.                 throw 'original positions of tabs cannot be calculated.';
  521.  
  522.             oldTabs.splice(baseTab._tPos, 1);
  523.             oldTabs.splice.apply(oldTabs, [oldTabs.indexOf(otherTabs[0]), otherTabs.length].concat(tabs));
  524.         }
  525.         else {
  526.             oldTabs.splice(oldBasePosition, 0, oldTabs.splice(baseTab._tPos, 1)[0]);
  527.         }
  528.  
  529.         // step 2: extract tabs which should be moved
  530.         var movedTabs = oldTabs.filter(function(aTab) {
  531.                     return otherTabs.indexOf(aTab) > -1 || aTab == baseTab;
  532.                 });
  533.  
  534.         // step 3: simulate rearranging
  535.         var rearranged = allTabs.filter(function(aTab) {
  536.                     return otherTabs.indexOf(aTab) < 0;
  537.                 });
  538.         rearranged.splice.apply(rearranged, [rearranged.indexOf(baseTab), 1].concat(movedTabs));
  539.  
  540.         // step 4: rearrange target tabs by the result of simulation
  541.         b.movingSelectedTabs = true;
  542.         rearranged.forEach(function(aTab, aNewPosition) {
  543.             if (otherTabs.indexOf(aTab) < 0) return;
  544.  
  545.             var previousTab = aNewPosition > 0 ? rearranged[aNewPosition-1] : null ;
  546.             if (previousTab)
  547.                 aNewPosition = previousTab._tPos + 1;
  548.             if (aNewPosition > aTab._tPos)
  549.                 aNewPosition--;
  550.             if (aTab._tPos != aNewPosition)
  551.                 b.moveTabTo(aTab, aNewPosition);
  552.         });
  553.         b.movingSelectedTabs = false;
  554.     },
  555.  
  556.     moveTabsByIndex : function MTS_moveTabsByIndex(aTabBrowser, aOldPositions, aNewPositions) 
  557.     {
  558.         // step 1: calculate new positions of all tabs
  559.         var restOldPositions = [];
  560.         var restNewPositions = [];
  561.         var tabs = this.getTabsArray(aTabBrowser);
  562.         tabs.forEach(function(aTab, aIndex) {
  563.             if (aOldPositions.indexOf(aIndex) < 0)
  564.                 restOldPositions.push(aIndex);
  565.             if (aNewPositions.indexOf(aIndex) < 0)
  566.                 restNewPositions.push(aIndex);
  567.         });
  568.  
  569.         // step 2: simulate rearranging
  570.         var rearranged = tabs.map(function(aTab, aOldPosition) {
  571.                 var index = aNewPositions.indexOf(aOldPosition);
  572.                 return tabs[(index > -1) ?
  573.                         aOldPositions[index] :
  574.                         restOldPositions[restNewPositions.indexOf(aOldPosition)] ];
  575.             });
  576.  
  577.         // step 3: rearrange target tabs by the result of simulation
  578.         aTabBrowser.movingSelectedTabs = true;
  579.         var allOldPositions = rearranged.map(function(aTab) {
  580.             return aTab._tPos;
  581.             });
  582.         rearranged.forEach(function(aTab, aNewPosition) {
  583.             var index = aOldPositions.indexOf(allOldPositions[aNewPosition]);
  584.             if (index < 0) return; // it's not a target!
  585.             var newPosition = aNewPositions[index ];
  586.             var previousTab = newPosition > 0 ? rearranged[newPosition-1] : null ;
  587.             if (previousTab)
  588.                 newPosition = previousTab._tPos + 1;
  589.             if (newPosition > aTab._tPos)
  590.                 newPosition--;
  591.             if (aTab._tPos != newPosition)
  592.                 aTabBrowser.moveTabTo(aTab, newPosition);
  593.         });
  594.         aTabBrowser.movingSelectedTabs = false;
  595.     },
  596.     
  597.     getOriginalPositions : function MTS_getOriginalPositions(aTabs, aBaseTab, aOldBasePosition) 
  598.     {
  599.         var newBasePosition = aBaseTab._tPos;
  600.         return aTabs.map(function(aTab) {
  601.                 if (aTab == aBaseTab)
  602.                     return aOldBasePosition;
  603.  
  604.                 var position = aTab._tPos;
  605.                 if (position <= aOldBasePosition && position > newBasePosition)
  606.                     position--;
  607.                 else if (position >= aOldBasePosition && position < newBasePosition)
  608.                     position++;
  609.  
  610.                 return position;
  611.             })
  612.             .sort();
  613.     },
  614.    
  615. // events 
  616.     
  617.     isEventFiredOnTabIcon : function MTS_isEventFiredOnTabIcon(aEvent) 
  618.     {
  619.         return this.evaluateXPath(
  620.                 'ancestor-or-self::*[contains(concat(" ",@class," "), " tab-icon ")]',
  621.                 aEvent.originalTarget || aEvent.target,
  622.                 XPathResult.BOOLEAN_TYPE
  623.             ).booleanValue;
  624.     },
  625.  
  626.     isEventFiredOnClickable : function MTS_isEventFiredOnClickable(aEvent) 
  627.     {
  628.         return this.evaluateXPath(
  629.                 'ancestor-or-self::*[contains(" button toolbarbutton scrollbar popup menupopup tooltip ", concat(" ", local-name(), " "))]',
  630.                 aEvent.originalTarget || aEvent.target,
  631.                 XPathResult.BOOLEAN_TYPE
  632.             ).booleanValue;
  633.     },
  634.  
  635.     getCloseboxFromEvent : function MTS_getCloseboxFromEvent(aEvent) 
  636.     {
  637.         return this.evaluateXPath(
  638.                 'ancestor-or-self::*[contains(concat(" ",@class," "), " tab-close-button ")]',
  639.                 aEvent.originalTarget || aEvent.target,
  640.                 XPathResult.FIRST_ORDERED_NODE_TYPE
  641.             ).singleNodeValue;
  642.     },
  643.  
  644.     isAccelKeyPressed : function MTS_isAccelKeyPressed(aEvent) 
  645.     {
  646.         return navigator.platform.toLowerCase().indexOf('mac') > -1 ? aEvent.metaKey : aEvent.ctrlKey ;
  647.     },
  648.   
  649. // fire custom events 
  650.     
  651.     fireDuplicatedEvent : function MTS_fireDuplicatedEvent(aNewTab, aSourceTab, aSourceEvent) 
  652.     {
  653.         var event = aNewTab.ownerDocument.createEvent('Events');
  654.         event.initEvent('MultipleTabHandler:TabDuplicate', true, false);
  655.         event.sourceTab = aSourceTab;
  656.         event.mayBeMove = aSourceEvent && !this.isAccelKeyPressed(aSourceEvent);
  657.         aNewTab.dispatchEvent(event);
  658.     },
  659.  
  660.     fireWindowMoveEvent : function MTS_fireWindowMoveEvent(aNewTab, aSourceTab) 
  661.     {
  662.         var event = document.createEvent('Events');
  663.         event.initEvent('MultipleTabHandler:TabWindowMove', true, false);
  664.         event.sourceTab = aSourceTab;
  665.         aNewTab.dispatchEvent(event);
  666.     },
  667.  
  668.     fireTabsClosingEvent : function MTS_fireTabsClosingEvent(aTabs) 
  669.     {
  670.         if (!aTabs || !aTabs.length) return false;
  671.         var d = aTabs[0].ownerDocument;
  672.         /* PUBLIC API */
  673.         var event = d.createEvent('Events');
  674.         event.initEvent('MultipleTabHandlerTabsClosing', true, true);
  675.         event.tabs = aTabs;
  676.         event.count = aTabs.length;
  677.         return this.getTabBrowserFromChild(aTabs[0]).dispatchEvent(event);
  678.     },
  679.  
  680.     fireTabsClosedEvent : function MTS_fireTabsClosedEvent(aTabBrowser, aTabs) 
  681.     {
  682.         if (!aTabs || !aTabs.length) return false;
  683.         aTabs = aTabs.filter(function(aTab) { return !aTab.parentNode; });
  684.         var d = aTabBrowser.ownerDocument;
  685.         /* PUBLIC API */
  686.         var event = d.createEvent('Events');
  687.         event.initEvent('MultipleTabHandlerTabsClosed', true, false);
  688.         event.tabs = aTabs;
  689.         event.count = aTabs.length;
  690.         aTabBrowser.dispatchEvent(event);
  691.     },
  692.   
  693.     createDragFeedbackImage : function MTS_createDragFeedbackImage(aNode) 
  694.     {
  695.         var tabs = this.getDraggedTabs(aNode);
  696.         if (tabs.length < 2) return null;
  697.  
  698.         var canvas = document.createElementNS('http://www.w3.org/1999/xhtml', 'canvas');
  699.         var offset = tabs[0].boxObject.height * 0.66;
  700.         var padding = offset * (tabs.length - 1);
  701.         var width = tabs[0].boxObject.width + (padding * 0.66);
  702.         var height = tabs[0].boxObject.height + padding;
  703.         canvas.width = width;
  704.         canvas.height = height;
  705.         try {
  706.             var ctx = canvas.getContext('2d');
  707.             ctx.clearRect(0, 0, width, height);
  708.             tabs.forEach(function(aTab, aIndex) {
  709.                 var box = aTab.boxObject;
  710.                 ctx.drawWindow(window, box.x, box.y, box.width, box.height, 'transparent');
  711.                 ctx.translate(offset * 0.66, offset);
  712.             }, this);
  713.             var image = new Image();
  714.             image.src = canvas.toDataURL()
  715.             return image;
  716.         }
  717.         catch(e) {
  718.             return null;
  719.         }
  720.     },
  721.     getDragFeedbackImageX : function MTS_getDragFeedbackImageX(aNode)
  722.     {
  723.         var tabs = this.getDraggedTabs(aNode);
  724.         if (tabs.length < 2) return 0;
  725.         return 16;
  726.     },
  727.     getDragFeedbackImageY : function MTS_getDragFeedbackImageY(aNode)
  728.     {
  729.         var tabs = this.getDraggedTabs(aNode);
  730.         if (tabs.length < 2) return 0;
  731.         return 16;
  732.     },
  733.     getDraggedTabs : function MTS_getDraggedTabs(aNode)
  734.     {
  735.         var b = this.getTabBrowserFromChild(aNode);
  736.         var tabs = b ? this.getSelectedTabs(b) : [] ;
  737.         return tabs;
  738.     },
  739.   
  740. /* Initializing */ 
  741.     
  742.     init : function MTS_init() 
  743.     {
  744.         if (!('gBrowser' in window)) return;
  745.  
  746.         window.addEventListener('mouseup', this, true);
  747.  
  748.         window.removeEventListener('load', this, false);
  749.         window.addEventListener('unload', this, false);
  750.  
  751.         window.addEventListener('UIOperationHistoryPreUndo:TabbarOperations', this, false);
  752.         window.addEventListener('UIOperationHistoryUndo:TabbarOperations', this, false);
  753.         window.addEventListener('UIOperationHistoryRedo:TabbarOperations', this, false);
  754.         window.addEventListener('UIOperationHistoryPostRedo:TabbarOperations', this, false);
  755.  
  756.         this.migratePrefs();
  757.         this.addPrefListener(this);
  758.         this.observe(null, 'nsPref:changed', 'extensions.multipletab.tabdrag.mode');
  759.         this.observe(null, 'nsPref:changed', 'extensions.multipletab.tabclick.accel.mode');
  760.         this.observe(null, 'nsPref:changed', 'extensions.multipletab.tabclick.shift.mode');
  761.         this.observe(null, 'nsPref:changed', 'extensions.multipletab.selectionStyle');
  762.         this.observe(null, 'nsPref:changed', 'extensions.multipletab.clipboard.linefeed');
  763.         this.observe(null, 'nsPref:changed', 'extensions.multipletab.clipboard.formats');
  764.  
  765. /*
  766.         if ('nsDragAndDrop' in window &&
  767.             'startDrag' in nsDragAndDrop) {
  768.             eval('nsDragAndDrop.startDrag = '+nsDragAndDrop.startDrag.toSource().replace(
  769.                 /(invokeDragSessionWithImage\([^\)]+,\s*)null\s*,\s*0,\s*0(\s*,[^\)]+\))/,
  770.                 '$1MultipleTabService.createDragFeedbackImage(aEvent.target), MultipleTabService.getDragFeedbackImageX(aEvent.target), MultipleTabService.getDragFeedbackImageY(aEvent.target)$2'
  771.             ));
  772.         }
  773. */
  774.  
  775.         if ('internalSave' in window) {
  776.             eval('window.internalSave = '+window.internalSave.toSource().replace(
  777.                 'var useSaveDocument =',
  778.                 <![CDATA[
  779.                     if (aChosenData && 'saveAsType' in aChosenData) {
  780.                         saveAsType = aChosenData.saveAsType;
  781.                         saveMode = SAVEMODE_FILEONLY | SAVEMODE_COMPLETE_TEXT;
  782.                     }
  783.                 $&]]>
  784.             ).replace(
  785.                 /(!aChosenData)( && useSaveDocument && saveAsType == kSaveAsType_Text)/,
  786.                 '($1 || "saveAsType" in aChosenData)$2'
  787.             ));
  788.         }
  789.  
  790.         [
  791.             'tm-freezeTab\tmultipletab-selection-freezeTabs',
  792.             'tm-protectTab\tmultipletab-selection-protectTabs',
  793.             'tm-lockTab\tmultipletab-selection-lockTabs'
  794.         ].forEach(function(aIDs) {
  795.             aIDs = aIDs.split('\t');
  796.             var source = document.getElementById(aIDs[0]);
  797.             var target = document.getElementById(aIDs[1]);
  798.             if (source)
  799.                 target.setAttribute('label', source.getAttribute('label'));
  800.         }, this);
  801.  
  802.         this.initTabBrowser(gBrowser);
  803.  
  804.         this.overrideExtensionsOnInit(); // hacks.js
  805.  
  806.         window.setTimeout(function(aSelf) { aSelf.delayedInit(); }, 0, this);
  807.     },
  808.     
  809.     preInit : function MTS_preInit() 
  810.     {
  811.         window.removeEventListener('DOMContentLoaded', this, false);
  812.  
  813.         if ('swapBrowsersAndCloseOther' in document.getElementById('content')) {
  814.             eval('window.BrowserStartup = '+window.BrowserStartup.toSource().replace(
  815.                 'gBrowser.swapBrowsersAndCloseOther(gBrowser.selectedTab, uriToLoad);',
  816.                 'if (!MultipleTabService.tearOffSelectedTabsFromRemote()) { $& }'
  817.             ));
  818.         }
  819.  
  820.         this.overrideExtensionsOnPreInit(); // hacks.js
  821.     },
  822.  
  823.     delayedInit : function MTS_delayedInit() 
  824.     {
  825.         this.overrideExtensionsOnDelayedInit(); // hacks.js
  826.     },
  827.  
  828.     kPREF_VERSION : 1,
  829.     migratePrefs : function MTS_migratePrefs() 
  830.     {
  831.         switch (this.getPref('extensions.multipletab.prefsVersion') || 0)
  832.         {
  833.             case 0:
  834.                 var clickModeValue = this.getPref('extensions.multipletab.tabclick.mode');
  835.                 if (clickModeValue !== null) {
  836.                     this.setPref('extensions.multipletab.tabclick.accel.mode', clickModeValue);
  837.                 }
  838.                 this.clearPref('extensions.multipletab.tabclick.mode');
  839.             default:
  840.                 break;
  841.         }
  842.         this.setPref('extensions.multipletab.prefsVersion', this.kPREF_VERSION);
  843.     },
  844.  
  845.     initTabBrowser : function MTS_initTabBrowser(aTabBrowser) 
  846.     {
  847.         aTabBrowser.addEventListener('TabOpen', this, true);
  848.         aTabBrowser.addEventListener('TabClose', this, true);
  849.         aTabBrowser.addEventListener('TabMove', this, true);
  850.         aTabBrowser.addEventListener('MultipleTabHandler:TabDuplicate', this, true);
  851.         aTabBrowser.addEventListener('MultipleTabHandler:TabWindowMove', this, true);
  852. //        aTabBrowser.mTabContainer.addEventListener('dragstart',   this, true);
  853.         aTabBrowser.mTabContainer.addEventListener('draggesture', this, true);
  854.         aTabBrowser.mTabContainer.addEventListener('mouseover',   this, true);
  855.         aTabBrowser.mTabContainer.addEventListener('mousemove',   this, true);
  856.         aTabBrowser.mTabContainer.addEventListener('mousedown',   this, true);
  857.  
  858. /*
  859.         eval('aTabBrowser.onDragStart = '+aTabBrowser.onDragStart.toSource().replace(
  860.             'aXferData.data.addDataForFlavour("text/unicode", URI.spec);',
  861.             <![CDATA[
  862.                 var selectedTabs = MultipleTabService.getSelectedTabs(this);
  863.                 if (MultipleTabService.isSelected(aEvent.target) &&
  864.                     MultipleTabService.allowMoveMultipleTabs) {
  865.                     aXferData.data.addDataForFlavour(
  866.                         'text/unicode',
  867.                         selectedTabs.map(function(aTab) {
  868.                             return aTab.linkedBrowser.currentURI.spec;
  869.                         }).join('\n')
  870.                     );
  871.                 }
  872.                 else {
  873.                     $&
  874.                 }
  875.             ]]>
  876.         ).replace(
  877.             /(aXferData.data.addDataForFlavour\("text\/html", [^\)]+\);)/,
  878.             <![CDATA[
  879.                 if (MultipleTabService.isSelected(aEvent.target) &&
  880.                     MultipleTabService.allowMoveMultipleTabs) {
  881.                     aXferData.data.addDataForFlavour(
  882.                         'text/html',
  883.                         selectedTabs.map(function(aTab) {
  884.                             return '<a href="' + aTab.linkedBrowser.currentURI.spec + '">' + aTab.label + '</a>';
  885.                         }).join('\n')
  886.                     );
  887.                 }
  888.                 else {
  889.                     $1
  890.                 }
  891.             ]]>
  892.         ));
  893. */
  894.  
  895.         eval('aTabBrowser.duplicateTab = '+aTabBrowser.duplicateTab.toSource().replace(
  896.             ')',
  897.             ', aSourceEvent)'
  898.         ).replace(
  899.             '{',
  900.             '{ return MultipleTabService.onDuplicateTab(function() {'
  901.         ).replace(
  902.             /(\}\)?)$/,
  903.             <![CDATA[
  904.                     },
  905.                     this,
  906.                     aTab,
  907.                     aSourceEvent
  908.                 );
  909.             $1]]>
  910.         ));
  911.  
  912.         if ('_onDrop' in aTabBrowser && 'swapBrowsersAndCloseOther' in aTabBrowser) {
  913.             eval('aTabBrowser._onDrop = '+aTabBrowser._onDrop.toSource().replace(
  914.                 /(this\.swapBrowsersAndCloseOther\([^;]+\);)/,
  915.                 'MultipleTabService.fireWindowMoveEvent(newTab, draggedTab); $1'
  916.             ));
  917.             aTabBrowser.__multipletab__canDoWindowMove = true;
  918.         }
  919.         else {
  920.             if ('onDrop' in aTabBrowser) {
  921.                 eval('aTabBrowser.onDrop = '+aTabBrowser.onDrop.toSource().replace(
  922.                     /(this\.duplicateTab\([^\)]+)(\))/g,
  923.                     '$1, aEvent$2'
  924.                 ));
  925.             }
  926.             aTabBrowser.__multipletab__canDoWindowMove = false;
  927.         }
  928.  
  929.         if ('_onDragEnd' in aTabBrowser) {
  930.             eval('aTabBrowser._onDragEnd = '+aTabBrowser._onDragEnd.toSource().replace(
  931.                 /([^\{\}\(\);]*this\.replaceTabWithWindow\()/,
  932.                 'if (MultipleTabService.isDraggingAllTabs(draggedTab)) return; $1'
  933.             ));
  934.         }
  935.  
  936.         this.initTabBrowserContextMenu(aTabBrowser);
  937.  
  938.         this.getTabsArray(aTabBrowser).forEach(function(aTab) {
  939.             this.initTab(aTab);
  940.         }, this);
  941.     },
  942.     
  943.     initTabBrowserContextMenu : function MTS_initTabBrowserContextMenu(aTabBrowser) 
  944.     {
  945.         var suffix = '-tabbrowser-'+(aTabBrowser.id || 'instance-'+parseInt(Math.random() * 65000));
  946.         var tabContextMenu = document.getAnonymousElementByAttribute(aTabBrowser, 'anonid', 'tabContextMenu');
  947.         var template = document.getElementById(this.kCONTEXT_MENU_TEMPLATE);
  948.         this.getArrayFromXPathResult('child::*[starts-with(@id, "multipletab-context-")]', template)
  949.             .concat(this.getArrayFromXPathResult('child::*[not(@id) or not(starts-with(@id, "multipletab-context-"))]', template))
  950.             .forEach(function(aItem) {
  951.                 let item = aItem.cloneNode(true);
  952.                 if (item.getAttribute('id'))
  953.                     item.setAttribute('id', item.getAttribute('id')+suffix);
  954.  
  955.                 let refNode = void(0);
  956.  
  957.                 let insertAfter = item.getAttribute(this.kINSERT_AFTER);
  958.                 if (insertAfter) {
  959.                     try {
  960.                         if (/^\s*xpath:/i.test(insertAfter)) {
  961.                             refNode = this.evaluateXPath(
  962.                                     insertAfter.replace(/^\s*xpath:\s*/i, ''),
  963.                                     tabContextMenu,
  964.                                     XPathResult.FIRST_ORDERED_NODE_TYPE
  965.                                 ).singleNodeValue;
  966.                             if (refNode) refNode = refNode.nextSibling;
  967.                         }
  968.                         else {
  969.                             eval('refNode = ('+insertAfter+').nextSibling');
  970.                         }
  971.                     }
  972.                     catch(e) {
  973.                     }
  974.                 }
  975.  
  976.                 let insertBefore = item.getAttribute(this.kINSERT_BEFORE);
  977.                 if (refNode === void(0) && insertBefore) {
  978.                     try {
  979.                         if (/^\s*xpath:/i.test(insertBefore)) {
  980.                             refNode = this.evaluateXPath(
  981.                                     insertBefore.replace(/^\s*xpath:\s*/i, ''),
  982.                                     tabContextMenu,
  983.                                     XPathResult.FIRST_ORDERED_NODE_TYPE
  984.                                 ).singleNodeValue;
  985.                         }
  986.                         else {
  987.                             eval('refNode = '+insertBefore);
  988.                         }
  989.                     }
  990.                     catch(e) {
  991.                     }
  992.                 }
  993.  
  994.                 tabContextMenu.insertBefore(item, refNode || null);
  995.             }, this);
  996.  
  997.         tabContextMenu.addEventListener('popupshowing', this, false);
  998.     },
  999.   
  1000.     initTab : function MTS_initTab(aTab) 
  1001.     {
  1002.         aTab.addEventListener('mousemove', this, true);
  1003.     },
  1004.   
  1005.     destroy : function MTS_destroy() 
  1006.     {
  1007.         this.destroyTabBrowser(gBrowser);
  1008.         window.addEventListener('mouseup', this, true);
  1009.  
  1010.         window.removeEventListener('unload', this, false);
  1011.         window.removeEventListener('UIOperationHistoryPreUndo:TabbarOperations', this, false);
  1012.         window.removeEventListener('UIOperationHistoryUndo:TabbarOperations', this, false);
  1013.         window.removeEventListener('UIOperationHistoryRedo:TabbarOperations', this, false);
  1014.         window.removeEventListener('UIOperationHistoryPostRedo:TabbarOperations', this, false);
  1015.  
  1016.         this.removePrefListener(this);
  1017.  
  1018.         this.getTabsArray(gBrowser).forEach(function(aTab) {
  1019.             this.destroyTab(aTab);
  1020.         }, this);
  1021.  
  1022.         var tabContextMenu = document.getAnonymousElementByAttribute(gBrowser, 'anonid', 'tabContextMenu');
  1023.         tabContextMenu.removeEventListener('popupshowing', this, false);
  1024.     },
  1025.     
  1026.     destroyTabBrowser : function MTS_destroyTabBrowser(aTabBrowser) 
  1027.     {
  1028.         aTabBrowser.removeEventListener('TabOpen', this, true);
  1029.         aTabBrowser.removeEventListener('TabClose', this, true);
  1030.         aTabBrowser.removeEventListener('TabMove', this, true);
  1031.         aTabBrowser.removeEventListener('MultipleTabHandler:TabDuplicate', this, true);
  1032.         aTabBrowser.removeEventListener('MultipleTabHandler:TabWindowMove', this, true);
  1033. //        aTabBrowser.mTabContainer.removeEventListener('dragstart',   this, true);
  1034.         aTabBrowser.mTabContainer.removeEventListener('draggesture', this, true);
  1035.         aTabBrowser.mTabContainer.removeEventListener('mouseover',   this, true);
  1036.         aTabBrowser.mTabContainer.removeEventListener('mousemove',   this, true);
  1037.         aTabBrowser.mTabContainer.removeEventListener('mousedown',   this, true);
  1038.  
  1039.         var tabContextMenu = document.getAnonymousElementByAttribute(aTabBrowser, 'anonid', 'tabContextMenu');
  1040.         tabContextMenu.removeEventListener('popupshowing', this, false);
  1041.     },
  1042.  
  1043.     destroyTab : function MTS_destroyTab(aTab) 
  1044.     {
  1045.         this.setSelection(aTab, false);
  1046.         if (!this.hasSelection())
  1047.             this.selectionModified = false;
  1048.  
  1049.         aTab.removeEventListener('mousemove', this, true);
  1050.     },
  1051.    
  1052. /* Event Handling */ 
  1053.     
  1054.     handleEvent : function MTS_handleEvent(aEvent) 
  1055.     {
  1056.         switch (aEvent.type)
  1057.         {
  1058.             case 'mousedown':
  1059.                 this.lastMouseDownX = aEvent.screenX;
  1060.                 this.lastMouseDownY = aEvent.screenY;
  1061.                 this.onTabClick(aEvent);
  1062.                 break;
  1063.  
  1064. //            case 'dragstart':
  1065. //                break;
  1066.  
  1067.             case 'draggesture':
  1068.                 this.onTabDragStart(aEvent);
  1069.                 break;
  1070.  
  1071.             case 'mouseup':
  1072.                 this.onTabDragEnd(aEvent);
  1073.                 break;
  1074.  
  1075.             case 'mouseover':
  1076.                 this.onTabDragEnter(aEvent);
  1077.                 break;
  1078.  
  1079.             case 'mousemove':
  1080.                 this.onTabDragOver(aEvent);
  1081.                 break;
  1082.  
  1083.             case 'TabOpen':
  1084.                 this.initTab(aEvent.originalTarget);
  1085.                 break;
  1086.  
  1087.             case 'TabClose':
  1088.                 this.destroyTab(aEvent.originalTarget);
  1089.                 break;
  1090.  
  1091.             case 'TabMove':
  1092.                 if (
  1093.                     this.isSelected(aEvent.originalTarget) &&
  1094.                     this.allowMoveMultipleTabs &&
  1095.                     !aEvent.currentTarget.movingSelectedTabs &&
  1096.                     (!('UndoTabService' in window) || UndoTabService.isUndoable())
  1097.                     )
  1098.                     this.moveBundledTabsOf(aEvent.originalTarget, aEvent);
  1099.                 break;
  1100.  
  1101.             case 'MultipleTabHandler:TabDuplicate':
  1102.                 if (
  1103.                     this.isSelected(aEvent.sourceTab) &&
  1104.                     this.allowMoveMultipleTabs &&
  1105.                     !aEvent.currentTarget.duplicatingSelectedTabs &&
  1106.                     (!('UndoTabService' in window) || UndoTabService.isUndoable())
  1107.                     )
  1108.                     this.duplicateBundledTabsOf(aEvent.originalTarget, aEvent.sourceTab, aEvent.mayBeMove);
  1109.                 break;
  1110.  
  1111.             case 'MultipleTabHandler:TabWindowMove':
  1112.                 if (
  1113.                     this.isSelected(aEvent.sourceTab) &&
  1114.                     this.allowMoveMultipleTabs &&
  1115.                     !aEvent.currentTarget.duplicatingSelectedTabs &&
  1116.                     (!('UndoTabService' in window) || UndoTabService.isUndoable())
  1117.                     )
  1118.                     this.importBundledTabsOf(aEvent.originalTarget, aEvent.sourceTab);
  1119.                 break;
  1120.  
  1121.             case 'DOMContentLoaded':
  1122.                 this.preInit();
  1123.                 break;
  1124.  
  1125.             case 'load':
  1126.                 this.init();
  1127.                 break;
  1128.  
  1129.             case 'unload':
  1130.                 this.destroy();
  1131.                 break;
  1132.  
  1133.             case 'popupshowing':
  1134.                 if (
  1135.                     aEvent.target.id != this.kSELECTION_MENU &&
  1136.                     this.hasSelection()
  1137.                     ) {
  1138.                     this.showSelectionPopup({
  1139.                         screenX : this.lastMouseDownX,
  1140.                         screenY : this.lastMouseDownY,
  1141.                     });
  1142.                     aEvent.preventDefault();
  1143.                     aEvent.stopPropagation();
  1144.                     return false;
  1145.                 }
  1146.                 this.enableMenuItems(aEvent.target);
  1147.                 this.showHideMenuItems(aEvent.target);
  1148.                 this.updateMenuItems(aEvent.target);
  1149.                 break;
  1150.  
  1151.  
  1152.             case 'UIOperationHistoryPreUndo:TabbarOperations':
  1153.                 switch (aEvent.entry.name)
  1154.                 {
  1155.                     case 'multipletab-tearOffTabs-our':
  1156.                     case 'multipletab-tearOffTabs-remote':
  1157.                         return this.onPreUndoTearOffTabsToNewWindow(aEvent);
  1158.                 }
  1159.                 break;
  1160.  
  1161.             case 'UIOperationHistoryUndo:TabbarOperations':
  1162.                 switch (aEvent.entry.name)
  1163.                 {
  1164.                     case 'multipletab-duplicateTabs':
  1165.                     case 'multipletab-closeTabs':
  1166.                         this.restoreTabFocus(aEvent.entry.data, aEvent.entry.data.oldSelected);
  1167.                         return;
  1168.                     case 'multipletab-importBundledTabs-target':
  1169.                     case 'multipletab-importBundledTabs-source':
  1170.                     case 'multipletab-duplicateBundledTabs-source':
  1171.                     case 'multipletab-duplicateBundledTabs-target':
  1172.                         this.restoreTabPositions(aEvent.entry.data.source);
  1173.                         return;
  1174.                     case 'multipletab-tearOffTabs-our':
  1175.                     case 'multipletab-tearOffTabs-remote':
  1176.                         return this.onUndoTearOffTabsToNewWindow(aEvent);
  1177.                 }
  1178.                 break;
  1179.  
  1180.             case 'UIOperationHistoryRedo:TabbarOperations':
  1181.                 switch (aEvent.entry.name)
  1182.                 {
  1183.                     case 'multipletab-tearOffTabs-our':
  1184.                     case 'multipletab-tearOffTabs-remote':
  1185.                         return this.onRedoTearOffTabsToNewWindow(aEvent);
  1186.                 }
  1187.                 break;
  1188.  
  1189.             case 'UIOperationHistoryPostRedo:TabbarOperations':
  1190.                 switch (aEvent.entry.name)
  1191.                 {
  1192.                     case 'multipletab-duplicateTabs':
  1193.                     case 'multipletab-closeTabs':
  1194.                         this.restoreTabFocus(aEvent.entry.data, aEvent.entry.data.newSelected);
  1195.                         return;
  1196.                     case 'multipletab-importBundledTabs-target':
  1197.                     case 'multipletab-importBundledTabs-source':
  1198.                     case 'multipletab-duplicateBundledTabs-source':
  1199.                     case 'multipletab-duplicateBundledTabs-target':
  1200.                         this.restoreTabPositions(aEvent.entry.data.target);
  1201.                         return;
  1202.                     case 'multipletab-tearOffTabs-our':
  1203.                     case 'multipletab-tearOffTabs-remote':
  1204.                         return this.onPostRedoTearOffTabsToNewWindow(aEvent);
  1205.                 }
  1206.                 break;
  1207.         }
  1208.     },
  1209.     restoreTabFocus : function MTS_restoreSelectedTab(aData, aSelected)
  1210.     {
  1211.         if (!aData || !aSelected)
  1212.             return;
  1213.  
  1214.         var target = UndoTabService.getTabOpetarionTargetsBy(aData);
  1215.         if (!target.browser)
  1216.             return;
  1217.  
  1218.         var selected = UndoTabService.getTargetById(aSelected, target.browser.mTabContainer);
  1219.         if (selected)
  1220.             target.browser.selectedTab = selected;
  1221.     },
  1222.     restoreTabPositions : function MTS_restoreTabPositions(aData)
  1223.     {
  1224.         if (!aData)
  1225.             return;
  1226.  
  1227.         var target = UndoTabService.getTabOpetarionTargetsBy(aData);
  1228.         if (!target.browser || (target.tabs.length != aData.positions.length))
  1229.             return;
  1230.  
  1231.         this.moveTabsByIndex(
  1232.             target.browser,
  1233.             target.tabs.map(function(aTab) {
  1234.                 return aTab._tPos;
  1235.             }),
  1236.             aData.positions
  1237.         );
  1238.     },
  1239.  
  1240.     onTabClick : function MTS_onTabClick(aEvent) 
  1241.     {
  1242.         if (aEvent.button != 0) return;
  1243.  
  1244.         var tab = this.getTabFromEvent(aEvent);
  1245.         if (tab) {
  1246.             var b = this.getTabBrowserFromChild(tab);
  1247.             if (aEvent.shiftKey) {
  1248.                 if (this.tabShiftClickMode != this.TAB_CLICK_MODE_SELECT)
  1249.                     return;
  1250.                 var tabs = b.mTabContainer.childNodes;
  1251.                 var inSelection = false;
  1252.                 this.getTabsArray(b).forEach(function(aTab) {
  1253.                     if (aTab.getAttribute('hidden') == 'true' ||
  1254.                         aTab.getAttribute('collapsed') == 'true')
  1255.                         return;
  1256.  
  1257.                     if (aTab == b.selectedTab ||
  1258.                         aTab == tab) {
  1259.                         inSelection = !inSelection;
  1260.                         this.setSelection(aTab, true);
  1261.                     }
  1262.                     else {
  1263.                         this.setSelection(aTab, inSelection);
  1264.                     }
  1265.                 }, this);
  1266.                 aEvent.preventDefault();
  1267.                 aEvent.stopPropagation();
  1268.                 return;
  1269.             }
  1270.             else if (this.isAccelKeyPressed(aEvent)) {
  1271.                 if (this.tabAccelClickMode != this.TAB_CLICK_MODE_SELECT) {
  1272.                     b.removeTab(tab);
  1273.                     return;
  1274.                 }
  1275.  
  1276.                 if (!this.selectionModified && !this.hasSelection())
  1277.                     this.setSelection(b.selectedTab, true);
  1278.  
  1279.                 this.toggleSelection(tab);
  1280.                 aEvent.preventDefault();
  1281.                 aEvent.stopPropagation();
  1282.                 return;
  1283.             }
  1284.             else if (this.tabDragMode != this.TAB_DRAG_MODE_DEFAULT) {
  1285.                 var delay = this.getPref('extensions.multipletab.tabdrag.delay');
  1286.                 if (delay > 0) {
  1287.                     this.cancelDelayedDragStart();
  1288.                     this.lastMouseDown = Date.now();
  1289.                     this.delayedDragStartTimer = window.setTimeout(this.delayedDragStart, delay, this, aEvent);
  1290.                 }
  1291.             }
  1292.         }
  1293.         if (this.selectionModified && !this.hasSelection())
  1294.             this.selectionModified = false;
  1295.  
  1296.         if (
  1297.             (!tab && !this.isEventFiredOnClickable(aEvent)) ||
  1298.             (tab && !this.isSelected(tab)) ||
  1299.             !this.allowMoveMultipleTabs
  1300.             )
  1301.             this.clearSelection();
  1302.     },
  1303.     
  1304.     delayedDragStart : function MTS_delayedDragStart(aSelf, aEvent) 
  1305.     {
  1306.         aSelf.clearSelection();
  1307.         aSelf.tabDragging = false; // cancel "dragging" before we start to drag it really.
  1308.         aSelf.delayedDragStartReady = true;
  1309.         aSelf.onTabDragStart(aEvent, true);
  1310.     },
  1311.     cancelDelayedDragStart : function MTS_cancelDelayedDragStart()
  1312.     {
  1313.         if (this.delayedDragStartTimer) {
  1314.             window.clearTimeout(this.delayedDragStartTimer);
  1315.             this.delayedDragStartTimer = null;
  1316.         }
  1317.     },
  1318.     delayedDragStartTimer : null,
  1319.   
  1320.     onTabDragStart : function MTS_onTabDragStart(aEvent, aIsTimeout) 
  1321.     {
  1322.         this.cancelDelayedDragStart();
  1323.  
  1324.         var tab = this.getTabFromEvent(aEvent);
  1325.         if (!tab) {
  1326.             this.lastMouseOverTarget = null;
  1327.             return;
  1328.         }
  1329.  
  1330.         if (
  1331.             tab.mOverCloseButton ||
  1332.             tab.tmp_mOverCloseButton // Tab Mix Plus
  1333.             ) {
  1334.             this.tabCloseboxDragging = true;
  1335.             this.lastMouseOverTarget = this.getCloseboxFromEvent(aEvent);
  1336.             this.clearSelectionSub(this.getSelectedTabs(this.getTabBrowserFromChild(tab)), this.kSELECTED);
  1337.             this.setReadyToClose(tab, true);
  1338.         }
  1339.         else if (
  1340.             this.isEventFiredOnTabIcon(aEvent) ||
  1341.             this.tabDragMode == this.TAB_DRAG_MODE_DEFAULT
  1342.             ) {
  1343.             return;
  1344.         }
  1345.         else {
  1346.             var delay = this.getPref('extensions.multipletab.tabdrag.delay');
  1347.             if (
  1348.                 delay > 0 &&
  1349.                 (Date.now() - this.lastMouseDown < delay) &&
  1350.                 !aIsTimeout
  1351.                 ) {
  1352.                 return
  1353.             }
  1354.             this.tabDragging = true;
  1355.             this.delayedDragStartReady = false;
  1356.             this.lastMouseOverTarget = tab;
  1357.             if (this.tabDragMode == this.TAB_DRAG_MODE_SELECT)
  1358.                 this.setSelection(tab, true);
  1359.         }
  1360.  
  1361.         aEvent.preventDefault();
  1362.         aEvent.stopPropagation();
  1363.     },
  1364.     tabDragging         : false,
  1365.     tabCloseboxDragging : false,
  1366.     lastMouseOverTarget : null,
  1367.     lastMouseDown       : 0,
  1368.  
  1369.     onTabDragEnd : function MTS_onTabDragEnd(aEvent) 
  1370.     {
  1371.         this.cancelDelayedDragStart();
  1372.  
  1373.         if (this.tabCloseboxDragging) {
  1374.             this.tabCloseboxDragging = false;
  1375.             this.closeTabs(this.getReadyToCloseTabs());
  1376.             this.clearSelection();
  1377.         }
  1378.         else if (this.delayedDragStartReady) {
  1379.             if (this.tabDragMode == this.TAB_DRAG_MODE_SELECT)
  1380.                 this.clearSelection();
  1381.         }
  1382.         else if (this.tabDragging) {
  1383.             this.tabDragging = false;
  1384.             if (this.hasSelection()) {
  1385.                 if (this.getPref('extensions.multipletab.tabdrag.autopopup'))
  1386.                     this.showSelectionPopup(aEvent, this.getPref('extensions.multipletab.tabdrag.autoclear'));
  1387.             }
  1388.             else {
  1389.                 this.clearSelection();
  1390.             }
  1391.         }
  1392.         this.delayedDragStartReady = false;
  1393.  
  1394.         this.lastMouseOverTarget = null;
  1395.     },
  1396.  
  1397.     onTabDragEnter : function MTS_onTabDragEnter(aEvent) 
  1398.     {
  1399.         if (!(
  1400.                 this.tabDragging ||
  1401.                 this.tabCloseboxDragging
  1402.             ) || this.isDisabled())
  1403.             return;
  1404.  
  1405.         var b = this.getTabBrowserFromChild(aEvent.originalTarget);
  1406.         var arrowscrollbox = b.mTabContainer.mTabstrip;
  1407.         if (aEvent.originalTarget == document.getAnonymousElementByAttribute(arrowscrollbox, 'class', 'scrollbutton-up')) {
  1408.             arrowscrollbox._startScroll(-1);
  1409.         }
  1410.         else if (aEvent.originalTarget == document.getAnonymousElementByAttribute(arrowscrollbox, 'class', 'scrollbutton-down')) {
  1411.             arrowscrollbox._startScroll(1);
  1412.         }
  1413.     },
  1414.  
  1415.     onTabDragOver : function MTS_onTabDragOver(aEvent) 
  1416.     {
  1417.         if (!(
  1418.                 this.tabDragging ||
  1419.                 this.tabCloseboxDragging
  1420.             ) || this.isDisabled())
  1421.             return;
  1422.  
  1423.         if (this.tabDragging || this.tabCloseboxDragging) {
  1424.             window['piro.sakura.ne.jp'].autoScroll.processAutoScroll(aEvent);
  1425.         }
  1426.  
  1427.         if (this.tabDragging) {
  1428.             var tab = this.getTabFromEvent(aEvent, true);
  1429.             if (tab == this.lastMouseOverTarget) return;
  1430.  
  1431.             if (!tab) {
  1432.                 this.lastMouseOverTarget = null;
  1433.                 return;
  1434.             }
  1435.  
  1436.             this.lastMouseOverTarget = tab;
  1437.  
  1438.             switch(this.tabDragMode)
  1439.             {
  1440.                 case this.TAB_DRAG_MODE_SELECT:
  1441.                     this.toggleSelection(tab);
  1442.                     break;
  1443.  
  1444.                 case this.TAB_DRAG_MODE_SWITCH:
  1445.                     var b = this.getTabBrowserFromChild(tab);
  1446.                     b.selectedTab = tab;
  1447.                     break;
  1448.  
  1449.                 default:
  1450.                     break;
  1451.             }
  1452.         }
  1453.         else if (this.tabCloseboxDragging) {
  1454.             if (aEvent.originalTarget == this.lastMouseOverTarget) return;
  1455.  
  1456.             this.lastMouseOverTarget = aEvent.originalTarget;
  1457.  
  1458.             if (!this.getCloseboxFromEvent(aEvent)) return;
  1459.  
  1460.             var tab = this.getTabFromEvent(aEvent, true);
  1461.             this.toggleReadyToClose(tab);
  1462.         }
  1463.     },
  1464.  
  1465.     // for drag and drop of selected tabs
  1466.     onDuplicateTab : function MTS_onDuplicateTab(aTask, aTabBrowser, aTab, aSourceEvent) 
  1467.     {
  1468.         if (
  1469.             !this.isSelected(aTab) ||
  1470.             !this.allowMoveMultipleTabs &&
  1471.             ('UndoTabService' in window && UndoTabService.isUndoable())
  1472.             )
  1473.             return aTask.call(aTabBrowser);
  1474.  
  1475.         var b = this.getTabBrowserFromChild(aTab);
  1476.         if (b.duplicatingSelectedTabs)
  1477.             return aTask.call(aTabBrowser);
  1478.  
  1479.         var tabs = this.getBundledTabsOf(aTab);
  1480.         if (tabs.length <= 1)
  1481.             return aTask.call(aTabBrowser);
  1482.  
  1483.         var newTab;
  1484.         if ('UndoTabService' in window && UndoTabService.isUndoable()) {
  1485.             var self = this;
  1486.             UndoTabService.doOperation(
  1487.                 function(aInfo) {
  1488.                     newTab = aTask.call(aTabBrowser);
  1489.                     self.fireDuplicatedEvent(newTab, aTab, aSourceEvent);
  1490.                 },
  1491.                 {
  1492.                     name  : 'multipletab-duplicateTab',
  1493.                     label : this.bundle.getFormattedString('undo_duplicateTabs_label', [tabs.length])
  1494.                 }
  1495.             );
  1496.         }
  1497.         else {
  1498.             newTab = aTask.call(aTabBrowser);
  1499.             this.fireDuplicatedEvent(newTab, aTab, aSourceEvent);
  1500.         }
  1501.         return newTab;
  1502.     },
  1503.   
  1504. /* Popup */ 
  1505.     
  1506.     get tabSelectionPopup() { 
  1507.         if (!this._tabSelectionPopup) {
  1508.             this._tabSelectionPopup = document.getElementById(this.kSELECTION_MENU);
  1509.         }
  1510.         return this._tabSelectionPopup;
  1511.     },
  1512.     _tabSelectionPopup : null,
  1513.  
  1514.     showSelectionPopup : function MTS_showSelectionPopup(aEvent, aAutoClearSelection) 
  1515.     {
  1516.         var popup = this.tabSelectionPopup;
  1517.         popup.hidePopup();
  1518.         popup.autoClearSelection = aAutoClearSelection;
  1519.         document.popupNode = this.browser.mTabContainer;
  1520.         if ('openPopupAtScreen' in popup) // Firefox 3
  1521.             popup.openPopupAtScreen(aEvent.screenX, aEvent.screenY, true);
  1522.         else
  1523.             popup.showPopup(
  1524.                 document.documentElement,
  1525.                 aEvent.screenX - document.documentElement.boxObject.screenX,
  1526.                 aEvent.screenY - document.documentElement.boxObject.screenY,
  1527.                 'popup'
  1528.             );
  1529.     },
  1530.  
  1531.     updateMenuItems : function MTS_updateMenuItems(aPopup) 
  1532.     {
  1533.         if (aPopup == this.tabSelectionPopup) {
  1534.             var lockedItem = document.getElementById('multipletab-selection-lockTabs');
  1535.             var protectItem = document.getElementById('multipletab-selection-protectTabs');
  1536.             var freezeItem = document.getElementById('multipletab-selection-freezeTabs');
  1537.             var tabs = this.getSelectedTabs();
  1538.  
  1539.             var locked = (lockedItem.getAttribute('hidden') == 'true') ?
  1540.                         false :
  1541.                         tabs.every(this._isTabLocked) ;
  1542.             var protected = (protectItem.getAttribute('hidden') == 'true') ?
  1543.                         false :
  1544.                         tabs.every(this._isTabProtected) ;
  1545.             var freezed = (freezeItem.getAttribute('hidden') == 'true') ?
  1546.                         false :
  1547.                         tabs.every(this._isTabFreezed) ;
  1548.  
  1549.             if (locked)
  1550.                 lockedItem.setAttribute('checked', true);
  1551.             else
  1552.                 lockedItem.removeAttribute('checked');
  1553.  
  1554.             if (protected)
  1555.                 protectItem.setAttribute('checked', true);
  1556.             else
  1557.                 protectItem.removeAttribute('checked');
  1558.  
  1559.             if (freezed)
  1560.                 freezeItem.setAttribute('checked', true);
  1561.             else
  1562.                 freezeItem.removeAttribute('checked');
  1563.         }
  1564.     },
  1565.  
  1566.     enableMenuItems : function MTS_enableMenuItems(aPopup) 
  1567.     {
  1568.         var tab = this.browser.mContextTab || this.browser.selectedTab;
  1569.  
  1570.         try {
  1571.             var removeLeft = document.evaluate(
  1572.                     'descendant::xul:menuitem[starts-with(@id, "multipletab-context-removeLeftTabs")]',
  1573.                     aPopup,
  1574.                     this.NSResolver,
  1575.                     XPathResult.FIRST_ORDERED_NODE_TYPE,
  1576.                     null
  1577.                 ).singleNodeValue;
  1578.             if (removeLeft) {
  1579.                 if (this.getPreviousTab(tab))
  1580.                     removeLeft.removeAttribute('disabled');
  1581.                 else
  1582.                     removeLeft.setAttribute('disabled', true);
  1583.             }
  1584.         }
  1585.         catch(e) {
  1586.         }
  1587.  
  1588.         try {
  1589.             var removeRight = document.evaluate(
  1590.                     'descendant::xul:menuitem[starts-with(@id, "multipletab-context-removeRightTabs")]',
  1591.                     aPopup,
  1592.                     this.NSResolver,
  1593.                     XPathResult.FIRST_ORDERED_NODE_TYPE,
  1594.                     null
  1595.                 ).singleNodeValue;
  1596.             if (removeRight) {
  1597.                 if (this.getNextTab(tab))
  1598.                     removeRight.removeAttribute('disabled');
  1599.                 else
  1600.                     removeRight.setAttribute('disabled', true);
  1601.             }
  1602.         }
  1603.         catch(e) {
  1604.         }
  1605.     },
  1606.  
  1607.     showHideMenuItems : function MTS_showHideMenuItems(aPopup) 
  1608.     {
  1609.         var b   = this.getTabBrowserFromChild(aPopup) || this.browser;
  1610.         var box = b.mTabContainer.mTabstrip || b.mTabContainer ;
  1611.         var isVertical = ((box.getAttribute('orient') || window.getComputedStyle(box, '').getPropertyValue('-moz-box-orient')) == 'vertical');
  1612.  
  1613.         var selectableItemsRegExp = new RegExp(
  1614.                 '^(multipletab-(?:context|selection)-('+
  1615.                 this.selectableItems.map(function(aItem) {
  1616.                     return aItem.name;
  1617.                 }).join('|')+
  1618.                 '))(:select)?$'
  1619.             );
  1620.  
  1621.         var selectType = {};
  1622.         this.selectableItems.forEach(function(aItem) {
  1623.             selectType[aItem.name] = this.getPref(aItem.key) < 0;
  1624.         }, this);
  1625.  
  1626.         var selectedTabs = this.getSelectedTabs(b);
  1627.         var tabbrowser = b;
  1628.         var tabs = this.getTabsArray(b);
  1629.         Array.slice(aPopup.childNodes).forEach(function(aNode, aIndex) {
  1630.             var label;
  1631.             if (
  1632.                 (isVertical && (label = aNode.getAttribute('label-vertical'))) ||
  1633.                 (!isVertical && (label = aNode.getAttribute('label-horizontal')))
  1634.                 )
  1635.                 aNode.setAttribute('label', label);
  1636.  
  1637.             var key = aNode.getAttribute('id').replace(/-tabbrowser-.*$/, '');
  1638.             var pref;
  1639.             if (selectableItemsRegExp.test(key)) {
  1640.                 key  = RegExp.$1
  1641.                 pref = this.getPref('extensions.multipletab.show.'+key) &&
  1642.                         (Boolean(RegExp.$3) == selectType[RegExp.$2]);
  1643.             }
  1644.             else {
  1645.                 pref = this.getPref('extensions.multipletab.show.'+key);
  1646.             }
  1647.  
  1648.             var available = aNode.getAttribute(this.kAVAILABLE);
  1649.             if (available) {
  1650.                 /* tabbrowser
  1651.                    tabs
  1652.                    selectedTabs */
  1653.                 eval('available = ('+available+')');
  1654.                 if (!available) pref = false;
  1655.             }
  1656.  
  1657.             if (pref === null) return;
  1658.  
  1659.             if (pref) {
  1660.                 aNode.removeAttribute('hidden');
  1661.                 var enabled = aNode.getAttribute(this.kENABLED);
  1662.                 if (enabled) {
  1663.                     /* tabbrowser
  1664.                        tabs
  1665.                        selectedTabs */
  1666.                     eval('enabled = ('+enabled+')');
  1667.                     if (enabled)
  1668.                         aNode.removeAttribute('disabled');
  1669.                     else
  1670.                         aNode.setAttribute('disabled', true);
  1671.                 }
  1672.             }
  1673.             else {
  1674.                 aNode.setAttribute('hidden', true);
  1675.             }
  1676.         }, this);
  1677.  
  1678.         var separators = this.getSeparators(aPopup);
  1679.         for (var i = separators.snapshotLength-1; i > -1; i--)
  1680.         {
  1681.             separators.snapshotItem(i).removeAttribute('hidden');
  1682.         }
  1683.  
  1684.         var separator;
  1685.         while (separator = this.getObsoleteSeparator(aPopup))
  1686.         {
  1687.             separator.setAttribute('hidden', true);
  1688.         }
  1689.     },
  1690.     
  1691.     getSeparators : function MTS_getSeparators(aPopup) 
  1692.     {
  1693.         try {
  1694.             var xpathResult = document.evaluate(
  1695.                     'descendant::xul:menuseparator',
  1696.                     aPopup,
  1697.                     this.NSResolver, // document.createNSResolver(document.documentElement),
  1698.                     XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
  1699.                     null
  1700.                 );
  1701.         }
  1702.         catch(e) {
  1703.             return { snapshotLength : 0 };
  1704.         }
  1705.         return xpathResult;
  1706.     },
  1707.  
  1708.     getObsoleteSeparator : function MTS_getObsoleteSeparator(aPopup) 
  1709.     {
  1710.         try {
  1711.             var xpathResult = document.evaluate(
  1712.                     'descendant::xul:menuseparator[not(@hidden)][not(following-sibling::*[not(@hidden)]) or not(preceding-sibling::*[not(@hidden)]) or local-name(following-sibling::*[not(@hidden)]) = "menuseparator"]',
  1713.                     aPopup,
  1714.                     this.NSResolver, // document.createNSResolver(document.documentElement),
  1715.                     XPathResult.FIRST_ORDERED_NODE_TYPE,
  1716.                     null
  1717.                 );
  1718.         }
  1719.         catch(e) {
  1720.             return null;
  1721.         }
  1722.         return xpathResult.singleNodeValue;
  1723.     },
  1724.   
  1725.     initCopyFormatItems : function MTS_initCopyFormatItems(aPopup) 
  1726.     {
  1727.         if (aPopup.formatsTimeStamp == this.formatsTimeStamp) return;
  1728.  
  1729.         aPopup.formatsTimeStamp = this.formatsTimeStamp;
  1730.  
  1731.         var separator = aPopup.getElementsByTagName('menuseparator')[0];
  1732.         var range = document.createRange();
  1733.         range.selectNodeContents(aPopup);
  1734.         range.setStartAfter(separator);
  1735.         range.deleteContents();
  1736.         if (this.formats.length) {
  1737.             separator.removeAttribute('hidden');
  1738.             let fragment = document.createDocumentFragment();
  1739.             this.formats.forEach(function(aItem) {
  1740.                 let item = document.createElement('menuitem');
  1741.                 item.setAttribute('label', aItem.label);
  1742.                 item.setAttribute('value', aItem.format);
  1743.                 item.setAttribute('format-type', aItem.id);
  1744.                 fragment.appendChild(item);
  1745.             }, this);
  1746.             range.insertNode(fragment);
  1747.         }
  1748.         else {
  1749.             separator.setAttribute('hidden', true);
  1750.         }
  1751.         range.detach();
  1752.     },
  1753.   
  1754. /* Commands */ 
  1755.     
  1756.     closeTabs : function MTS_closeTabs(aTabs) 
  1757.     {
  1758.         if (!aTabs) return;
  1759.  
  1760.         if (!this.warnAboutClosingTabs(aTabs.length))
  1761.             return;
  1762.  
  1763.         this.closeTabsInternal(aTabs);
  1764.     },
  1765.     
  1766.     closeTabsInternal : function MTS_closeTabsInternal(aTabs) 
  1767.     {
  1768.         if (!aTabs.length) return;
  1769.  
  1770.         /* PUBLIC API */
  1771.         if (!this.fireTabsClosingEvent(aTabs))
  1772.             return;
  1773.  
  1774.         aTabs = this.sortTabs(aTabs);
  1775.         if (this.getPref('extensions.multipletab.close.direction') == this.CLOSE_DIRECTION_LAST_TO_START)
  1776.             aTabs.reverse();
  1777.  
  1778.         var w = aTabs[0].ownerDocument.defaultView;
  1779.         var b = this.getTabBrowserFromChild(aTabs[0]);
  1780.         var closeSelectedLast = this.getPref('extensions.multipletab.close.selectedTab.last');
  1781.  
  1782.         var self = this;
  1783.         var operation = function() {
  1784.             var selected;
  1785.             aTabs.forEach(function(aTab) {
  1786.                 if (closeSelectedLast && aTab.selected)
  1787.                     selected = aTab;
  1788.                 else
  1789.                     b.removeTab(aTab);
  1790.             });
  1791.             if (selected)
  1792.                 b.removeTab(selected);
  1793.         };
  1794.  
  1795.         w['piro.sakura.ne.jp'].stopRendering.stop();
  1796.         if ('UndoTabService' in window && UndoTabService.isUndoable()) {
  1797.             let data = UndoTabService.getTabOpetarionTargetsData({
  1798.                     browser : b
  1799.                     }, {
  1800.                     oldSelected : UndoTabService.getId(b.selectedTab)
  1801.                 });
  1802.             UndoTabService.doOperation(
  1803.                 operation,
  1804.                 {
  1805.                     name  : 'multipletab-closeTabs',
  1806.                     label : this.bundle.getFormattedString('undo_closeTabs_label', [aTabs.length]),
  1807.                     data  : data
  1808.                 }
  1809.             );
  1810.             data.newSelected = UndoTabService.getId(b.selectedTab);
  1811.         }
  1812.         else {
  1813.             operation();
  1814.         }
  1815.         w['piro.sakura.ne.jp'].stopRendering.start();
  1816.  
  1817.         /* PUBLIC API */
  1818.         this.fireTabsClosedEvent(b, aTabs);
  1819.  
  1820.         aTabs = null;
  1821.     },
  1822.     CLOSE_DIRECTION_START_TO_LAST : 0,
  1823.     CLOSE_DIRECTION_LAST_TO_START : 1,
  1824.   
  1825.     closeSimilarTabsOf : function MTS_closeSimilarTabsOf(aCurrentTab, aTabs) 
  1826.     {
  1827.         if (!aCurrentTab) return;
  1828.  
  1829.         var removeTabs = this.getSimilarTabsOf(aCurrentTab, aTabs);
  1830.         var count = removeTabs.length;
  1831.         if (!count || !this.warnAboutClosingTabs(count))
  1832.             return;
  1833.  
  1834.         var b = this.getTabBrowserFromChild(aCurrentTab);
  1835.         this.closeTabsInternal(removeTabs);
  1836.     },
  1837.  
  1838.     closeOtherTabs : function MTS_closeOtherTabs(aTabs) 
  1839.     {
  1840.         if (!aTabs || !aTabs.length) return;
  1841.  
  1842.         aTabs = Array.slice(aTabs);
  1843.         var b = this.getTabBrowserFromChild(aTabs[0]);
  1844.         var tabs = this.getTabsArray(b);
  1845.  
  1846.         if (!this.warnAboutClosingTabs(tabs.length - aTabs.length))
  1847.             return;
  1848.  
  1849.         var removeTabs = [];
  1850.         tabs.forEach(function(aTab) {
  1851.             if (aTabs.indexOf(aTab) < 0) removeTabs.push(aTab);
  1852.         });
  1853.  
  1854.         this.closeTabsInternal(removeTabs);
  1855.     },
  1856.  
  1857.     reloadTabs : function MTS_reloadTabs(aTabs) 
  1858.     {
  1859.         if (!aTabs) return;
  1860.  
  1861.         aTabs = this.filterBlankTabs(aTabs);
  1862.         if (!aTabs.length) return;
  1863.  
  1864.         var b = this.getTabBrowserFromChild(aTabs[0]);
  1865.         aTabs.forEach(function(aTab) {
  1866.             if (!this.ensureLoaded(aTab))
  1867.                 b.reloadTab(aTab);
  1868.         }, this);
  1869.     },
  1870.  
  1871.     saveTabs : function MTS_saveTabs(aTabs, aSaveType, aFolder) 
  1872.     {
  1873.         if (!aTabs) return;
  1874.  
  1875.         aTabs = this.filterBlankTabs(aTabs);
  1876.  
  1877.         if (aSaveType === void(0)) {
  1878.             aSaveType = this.getPref('extensions.multipletab.saveTabs.saveType');
  1879.         }
  1880.         if (aSaveType < 0) {
  1881.             aSaveType = this.kSAVE_TYPE_FILE;
  1882.         }
  1883.  
  1884.         if (aTabs.length == 1) {
  1885.             this.ensureLoaded(aTabs[0]);
  1886.             var saveType = aSaveType;
  1887.             if (aSaveType & this.kSAVE_TYPE_TEXT &&
  1888.                 !this.shouldConvertTabToText(aTabs[0], aSaveType)) {
  1889.                 aSaveType = this.kSAVE_TYPE_COMPLETE;
  1890.             }
  1891.             this.saveOneTab(aTabs[0], null, aSaveType);
  1892.             return;
  1893.         }
  1894.  
  1895.         var folder = aFolder || this.selectFolder(this.bundle.getString('saveTabs_chooseFolderTitle'));
  1896.         if (!folder) return;
  1897.  
  1898.         var fileExistence = {};
  1899.         aTabs.forEach(function(aTab) {
  1900.             if (this.ensureLoaded(aTab)) {
  1901.                 window.setTimeout(function(aSelf) {
  1902.                     arguments.callee.call(aSelf);
  1903.                 }, 200, this);
  1904.                 return;
  1905.             }
  1906.             var b = aTab.linkedBrowser;
  1907.             var destFile = folder.clone();
  1908.             var uri = b.currentURI;
  1909.             var shouldConvertToText = this.shouldConvertTabToText(aTab, aSaveType);
  1910.             var fileInfo = new FileInfo(aTab.label);
  1911.             initFileInfo(
  1912.                 fileInfo,
  1913.                 uri.spec,
  1914.                 b.contentDocument.characterSet,
  1915.                 b.contentDocument,
  1916.                 (shouldConvertToText ? 'text/plain' : b.contentDocument.contentType ),
  1917.                 null
  1918.             );
  1919.             var base = fileInfo.fileName;
  1920.             var extension = shouldConvertToText ? '.txt' : '.'+fileInfo.fileExt ;
  1921.             if (base.indexOf(extension) == base.length - extension.length) {
  1922.                 base = base.substring(0, base.length - extension.length);
  1923.             }
  1924.             var fileName = '';
  1925.             var count = 2;
  1926.             var existingFile;
  1927.             do {
  1928.                 fileName = fileName ? base+'('+(count++)+')'+extension : base+extension ;
  1929.                 destFile = folder.clone();
  1930.                 destFile.append(fileName);
  1931.             }
  1932.             while (destFile.exists() || destFile.path in fileExistence);
  1933.             fileExistence[destFile.path] = true;
  1934.             var saveType = aSaveType;
  1935.             if (saveType & this.kSAVE_TYPE_TEXT && !shouldConvertToText) {
  1936.                 saveType = this.kSAVE_TYPE_COMPLETE;
  1937.             }
  1938.             window.setTimeout(function(aSelf) {
  1939.                 aSelf.saveOneTab(aTab, destFile, saveType);
  1940.             }, 200, this);
  1941.         }, this);
  1942.     },
  1943.     
  1944.     kSAVE_TYPE_FILE     : 0, 
  1945.     kSAVE_TYPE_COMPLETE : 1,
  1946.     kSAVE_TYPE_TEXT     : 2,
  1947.  
  1948.     shouldConvertTabToText : function MTS_shouldConvertTabToText(aTab, aSaveType) 
  1949.     {
  1950.         return(
  1951.             aSaveType == this.kSAVE_TYPE_TEXT &&
  1952.             GetSaveModeForContentType(aTab.linkedBrowser.contentDocument.contentType, aTab.linkedBrowser.contentDocument) & SAVEMODE_COMPLETE_TEXT
  1953.         );
  1954.     },
  1955.  
  1956.     selectFolder : function MTS_selectFolder(aTitle) 
  1957.     {
  1958.         var picker = Components
  1959.                         .classes['@mozilla.org/filepicker;1']
  1960.                         .createInstance(Components.interfaces.nsIFilePicker);
  1961.         picker.init(window, aTitle, picker.modeGetFolder);
  1962.         var downloadDir = this.getPref('browser.download.dir', Components.interfaces.nsILocalFile);
  1963.         if (downloadDir) picker.displayDirectory = downloadDir;
  1964.         picker.appendFilters(picker.filterAll);
  1965.         if (picker.show() == picker.returnOK) {
  1966.             return picker.file.QueryInterface(Components.interfaces.nsILocalFile);
  1967.         }
  1968.         return null;
  1969.     },
  1970.  
  1971.     saveOneTab : function MTS_saveOneTab(aTab, aDestFile, aSaveType) 
  1972.     {
  1973.         var b = aTab.linkedBrowser;
  1974.         var uri = b.currentURI;
  1975.  
  1976.         var autoChosen = aDestFile ? new AutoChosen(aDestFile, uri) : null ;
  1977.         if (autoChosen && aSaveType == this.kSAVE_TYPE_TEXT) {
  1978.             autoChosen.saveAsType = kSaveAsType_Text;
  1979.         }
  1980.  
  1981.         internalSave(
  1982.             uri.spec,
  1983.             (aSaveType != this.kSAVE_TYPE_FILE ? b.contentDocument : null ),
  1984.             null, // default file name
  1985.             null, // content disposition
  1986.             b.contentDocument.contentType,
  1987.             false, // should bypass cache?
  1988.             null, // title of picker
  1989.             autoChosen,
  1990.             b.referringURI, // referrer
  1991.             true, // skip prompt?
  1992.             null // cache key
  1993.         );
  1994.     },
  1995.   
  1996.     addBookmarkFor : function MTS_addBookmarkFor(aTabs, aFolderName) 
  1997.     {
  1998.         var isTSTBookmarksTreeStructureAvailable = (
  1999.                 'TreeStyleTabBookmarksService' in window &&
  2000.                 'beginAddBookmarksFromTabs' in TreeStyleTabBookmarksService &&
  2001.                 'endAddBookmarksFromTabs' in TreeStyleTabBookmarksService
  2002.             );
  2003.         if (isTSTBookmarksTreeStructureAvailable)
  2004.             TreeStyleTabBookmarksService.beginAddBookmarksFromTabs(aTabs);
  2005.         try {
  2006.             window['piro.sakura.ne.jp'].bookmarkMultipleTabs.addBookmarkFor(aTabs, aFolderName);
  2007.         }
  2008.         catch(e) {
  2009.         }
  2010.         if (isTSTBookmarksTreeStructureAvailable)
  2011.             TreeStyleTabBookmarksService.endAddBookmarksFromTabs();
  2012.     },
  2013.  
  2014.     printTabs : function MTS_printTabs(aTabs) 
  2015.     {
  2016.         if (!('PrintAllTabs' in window)) return;
  2017.  
  2018.         aTabs.forEach(this.ensureLoaded, this);
  2019.  
  2020.         PrintAllTabs.__multipletab__printNodes = aTabs.map(function(aTab) {
  2021.             return aTab._tPos;
  2022.         });
  2023.         PrintAllTabs.onMenuItemCommand(null, false, false);
  2024.         PrintAllTabs.__multipletab__printNodes = null;
  2025.     },
  2026.  
  2027.     duplicateTabs : function MTS_duplicateTabs(aTabs) 
  2028.     {
  2029.         if (!aTabs || !aTabs.length) return [];
  2030.  
  2031.         var b = this.getTabBrowserFromChild(aTabs[0]);
  2032.         var w = b.ownerDocument.defaultView;
  2033.         var shouldSelectAfter = this.getPref('extensions.multipletab.selectAfter.duplicate');
  2034.         var duplicatedTabs;
  2035.  
  2036.         var self = this;
  2037.         var operation = function() {
  2038.             duplicatedTabs = self.duplicateTabsInternal(b, aTabs);
  2039.             if (shouldSelectAfter)
  2040.                 duplicatedTabs.forEach(function(aTab) {
  2041.                     self.setSelection(aTab, true);
  2042.                 });
  2043.         };
  2044.         w['piro.sakura.ne.jp'].stopRendering.stop();
  2045.         if ('UndoTabService' in window && UndoTabService.isUndoable()) {
  2046.             let data = UndoTabService.getTabOpetarionTargetsData({
  2047.                     browser : b
  2048.                     }, {
  2049.                     oldSelected : UndoTabService.getId(b.selectedTab)
  2050.                 });
  2051.             UndoTabService.doOperation(
  2052.                 operation,
  2053.                 {
  2054.                     name  : 'multipletab-duplicateTabs',
  2055.                     label : this.bundle.getFormattedString('undo_duplicateTabs_label', [aTabs.length]),
  2056.                     data  : data
  2057.                 }
  2058.             );
  2059.             data.newSelected = UndoTabService.getId(b.selectedTab);
  2060.         }
  2061.         else {
  2062.             operation();
  2063.         }
  2064.         w['piro.sakura.ne.jp'].stopRendering.start();
  2065.  
  2066.         return duplicatedTabs;
  2067.     },
  2068.     
  2069.     duplicateTabsInternal : function MTS_duplicateTabsInternal(aTabBrowser, aTabs) 
  2070.     {
  2071.         var max = aTabs.length;
  2072.         if (!max) return [];
  2073.  
  2074.         aTabs = this.sortTabs(aTabs);
  2075.  
  2076.         aTabs.forEach(this.ensureLoaded, this);
  2077.  
  2078.         var b = aTabBrowser;
  2079.         var w = b.ownerDocument.defaultView;
  2080.         var selectedIndex = aTabs.indexOf(b.selectedTab);
  2081.  
  2082.         this.duplicatingTabs = true;
  2083.  
  2084.         w['piro.sakura.ne.jp'].stopRendering.stop();
  2085.  
  2086.         this.clearSelection(b);
  2087.  
  2088.         var duplicatedTabs = aTabs.map(function(aTab) {
  2089.                 var state = this.evalInSandbox('('+this.SessionStore.getTabState(aTab)+')');
  2090.                 this._clearTabValueKeys.forEach(function(aKey) {
  2091.                     delete state.extData[aKey];
  2092.                 });
  2093.                 state = state.toSource();
  2094.                 var tab = b.addTab();
  2095.                 this.SessionStore.setTabState(tab, state);
  2096.                 return tab;
  2097.             }, this);
  2098.  
  2099.         this.clearSelection(b);
  2100.  
  2101.         if (selectedIndex > -1)
  2102.             b.selectedTab = duplicatedTabs[selectedIndex];
  2103.  
  2104.         w['piro.sakura.ne.jp'].stopRendering.start();
  2105.  
  2106.         w.setTimeout(function(aSelf) {
  2107.             aSelf.duplicatingTabs = false;
  2108.         }, 0, this);
  2109.  
  2110.         return duplicatedTabs;
  2111.     },
  2112.   
  2113.     splitWindowFromTabs : function MTS_splitWindowFromTabs(aTabs, aRemoteWindow) 
  2114.     {
  2115.         if (!aTabs || !aTabs.length) return null;
  2116.  
  2117.         aTabs.forEach(this.ensureLoaded, this);
  2118.  
  2119.         var b = this.getTabBrowserFromChild(aTabs[0]);
  2120.  
  2121.         if (!aRemoteWindow) {
  2122.             let self = this;
  2123.             aRemoteWindow = window.openDialog(location.href, '_blank', 'chrome,all,dialog=no', 'about:blank');
  2124.             aRemoteWindow.addEventListener('load', function() {
  2125.                 aRemoteWindow.removeEventListener('load', arguments.callee, false);
  2126.                 aRemoteWindow.setTimeout(function() {
  2127.                     self.tearOffTabsToNewWindow(aTabs, aRemoteWindow);
  2128.                 }, 0);
  2129.             }, false);
  2130.         }
  2131.         else {
  2132.             this.tearOffTabsToNewWindow(aTabs, aRemoteWindow);
  2133.         }
  2134.  
  2135.         return aRemoteWindow;
  2136.     },
  2137.     
  2138.     tearOffTabsToNewWindow : function MTS_tearOffTabsToNewWindow(aTabs, aRemoteWindow) 
  2139.     {
  2140.         var ourBrowser    = this.getTabBrowserFromChild(aTabs[0]);
  2141.         var ourWindow     = ourBrowser.ownerDocument.defaultView;
  2142.         var remoteBrowser = aRemoteWindow.gBrowser;
  2143.         var ourService    = ourWindow.MultipleTabService;
  2144.         var remoteService = aRemoteWindow.MultipleTabService;
  2145.  
  2146.         var selectAfter = this.getPref('extensions.multipletab.selectAfter.move');
  2147.  
  2148.         var operation = function(aOurParams, aRemoteParams, aData) {
  2149.                 var allSelected = true;
  2150.                 var selectionState = aTabs.map(function(aTab) {
  2151.                         var selected = ourService.isSelected(aTab);
  2152.                         if (!selected) allSelected = false;
  2153.                         return selected;
  2154.                     });
  2155.  
  2156.                 remoteService.duplicatingTabs = true;
  2157.                 aRemoteWindow['piro.sakura.ne.jp'].stopRendering.stop();
  2158.  
  2159.                 if (aOurParams)
  2160.                     aOurParams.wait();
  2161.                 if (aRemoteParams)
  2162.                     aRemoteParams.wait();
  2163.  
  2164.                 aRemoteWindow.setTimeout(function() {
  2165.                     var remoteBrowser = aRemoteWindow.gBrowser;
  2166.                     var importedTabs = remoteService.importTabsTo(aTabs, remoteBrowser);
  2167.                     remoteService.clearSelection(remoteBrowser);
  2168.                     remoteService.getTabsArray(remoteBrowser)
  2169.                         .forEach(function(aTab) {
  2170.                             var index = importedTabs.indexOf(aTab);
  2171.                             if (index > -1) {
  2172.                                 if (
  2173.                                     !allSelected &&
  2174.                                     selectionState[index] &&
  2175.                                     selectAfter
  2176.                                     ) {
  2177.                                     remoteService.setSelection(aTab, true);
  2178.                                 }
  2179.                             }
  2180.                             else {
  2181.                                 // causes error. why?
  2182.                                 // remoteService.irrevocableRemoveTab(aTab, remoteBrowser);
  2183.                                 remoteBrowser.removeTab(aTab);
  2184.                             }
  2185.                         });
  2186.                     aRemoteWindow['piro.sakura.ne.jp'].stopRendering.start();
  2187.  
  2188.                     if (aData) {
  2189.                         aData.remote.positions = [];
  2190.                         aData.remote.tabs = importedTabs.map(function(aTab) {
  2191.                             aData.remote.positions.push(aTab._tPos);
  2192.                             return aRemoteWindow.UndoTabService.getId(aTab);
  2193.                         });
  2194.                     }
  2195.                     if (aOurParams)
  2196.                         aOurParams.continue();
  2197.                     if (aRemoteParams)
  2198.                         aRemoteParams.continue();
  2199.                 }, 0);
  2200.             };
  2201.  
  2202.         if ('UndoTabService' in window && UndoTabService.isUndoable()) {
  2203.             let data = {
  2204.                     our : UndoTabService.getTabOpetarionTargetsData({
  2205.                         window  : ourWindow,
  2206.                         browser : ourBrowser,
  2207.                         tabs    : aTabs
  2208.                         }, {
  2209.                         selected  : UndoTabService.getId(ourBrowser.selectedTab),
  2210.                         positions : aTabs.map(function(aTab) {
  2211.                             return aTab._tPos;
  2212.                         })
  2213.                     }),
  2214.                     remote : UndoTabService.getTabOpetarionTargetsData({
  2215.                         window  : aRemoteWindow,
  2216.                         browser : remoteBrowser
  2217.                         }, {
  2218.                         width  : aRemoteWindow.outerWidth,
  2219.                         height : aRemoteWindow.outerHeight,
  2220.                         x      : aRemoteWindow.screenX,
  2221.                         y      : aRemoteWindow.screenY
  2222.                     })
  2223.                 };
  2224.             data.our.entry = {
  2225.                 name  : 'multipletab-tearOffTabs-our',
  2226.                 label : this.bundle.getFormattedString('undo_splitWindowFromTabs_label', [aTabs.length]),
  2227.                 data  : data
  2228.             };
  2229.             data.remote.entry = {
  2230.                 name  : 'multipletab-tearOffTabs-remote',
  2231.                 label : this.bundle.getFormattedString('undo_splitWindowFromTabs_label', [aTabs.length]),
  2232.                 data  : data
  2233.             };
  2234.             UndoTabService.doOperation(
  2235.                 function(aOurParams) {
  2236.                     UndoTabService.doOperation(
  2237.                         function(aRemoteParams) {
  2238.                             operation(aOurParams, aRemoteParams, data);
  2239.                         },
  2240.                         aRemoteWindow,
  2241.                         data.remote.entry
  2242.                     );
  2243.                 },
  2244.                 ourWindow,
  2245.                 data.our.entry
  2246.             );
  2247.         }
  2248.         else {
  2249.             operation();
  2250.         }
  2251.  
  2252.         return aRemoteWindow;
  2253.     },
  2254.     onPreUndoTearOffTabsToNewWindow : function MTS_onPreUndoTearOffTabsToNewWindow(aEvent)
  2255.     {
  2256.         var remote = UndoTabService.getTabOpetarionTargetsBy(aEvent.entry.data.remote);
  2257.         if (!remote.window || !remote.browser)
  2258.             return;
  2259.  
  2260.         remote.browser.addTab('about:blank'); // to prevent browser's auto-close
  2261.  
  2262.         data.remote.width  = remote.window.outerWidth;
  2263.         data.remote.height = remote.window.outerHeight;
  2264.         data.remote.x      = remote.window.screenX;
  2265.         data.remote.y      = remote.window.screenY;
  2266.     },
  2267.     onUndoTearOffTabsToNewWindow : function MTS_onUndoTearOffTabsToNewWindow(aEvent)
  2268.     {
  2269.         var entry  = aEvent.entry;
  2270.         var data   = entry.data;
  2271.         var our    = UndoTabService.getTabOpetarionTargetsBy(data.our);
  2272.         var remote = UndoTabService.getTabOpetarionTargetsBy(data.remote);
  2273.         if (!our.window || !remote.window)
  2274.             return aEvent.preventDefault();
  2275.  
  2276.         var tabs = our.tabs;
  2277.         if (entry == data.remote.entry) {
  2278.             if (remote.tabs.length == data.remote.tabs.length) {
  2279.                 tabs = remote.window.MultipleTabService.importTabsTo(remote.tabs, our.browser);
  2280.             }
  2281.             UndoTabService.fakeUndo(our.window, data.our.entry);
  2282.         }
  2283.  
  2284.         if (tabs.length == data.our.tabs.length) {
  2285.             this.moveTabsByIndex(
  2286.                 our.browser,
  2287.                 tabs.map(function(aTab, aIndex) {
  2288.                     UndoTabService.setElementId(aTab, data.our.tabs[aIndex]);
  2289.                     return aTab._tPos;
  2290.                 }),
  2291.                 data.our.positions
  2292.             );
  2293.         }
  2294.  
  2295.         var selected = UndoTabService.getTargetById(data.our.selected, data.our.browser.mTabContainer);
  2296.         if (selected)
  2297.             our.browser.selectedTab = selected;
  2298.  
  2299.         if (remote.browser)
  2300.             this.closeOwner(remote.browser);
  2301.     },
  2302.     onRedoTearOffTabsToNewWindow : function MTS_onRedoTearOffTabsToNewWindow(aEvent)
  2303.     {
  2304.         var entry  = aEvent.entry;
  2305.         var data   = entry.data;
  2306.         var remote = UndoTabService.getTabOpetarionTargetsBy(data.remote);
  2307.  
  2308.         // When the window was already reopened by other redo processes,
  2309.         // then use it.
  2310.         if (remote.window) {
  2311.             remote.window.resizeTo(data.remote.width, data.remote.height);
  2312.             remote.window.moveTo(data.remote.x, data.remote.y);
  2313.             return;
  2314.         }
  2315.  
  2316.         aEvent.wait();
  2317.         var remoteWindow = window.openDialog(location.href, '_blank', 'chrome,all,dialog=no', 'about:blank');
  2318.         remoteWindow.addEventListener('load', function() {
  2319.             remoteWindow.removeEventListener('load', arguments.callee, false);
  2320.             data.remote.window = UndoTabService.setWindowId(remoteWindow, data.remote.window);
  2321.             remoteWindow.resizeTo(data.remote.width, data.remote.height);
  2322.             remoteWindow.moveTo(data.remote.x, data.remote.y);
  2323.             data.remote.browser = UndoTabService.setElementId(remoteWindow.gBrowser, data.remote.browser);
  2324.             remoteWindow.setTimeout(function() {
  2325.                 aEvent.continue();
  2326.             }, 0);
  2327.         }, false);
  2328.     },
  2329.     onPostRedoTearOffTabsToNewWindow : function MTS_onPostRedoTearOffTabsToNewWindow(aEvent)
  2330.     {
  2331.         var entry  = aEvent.entry;
  2332.         var data   = entry.data;
  2333.         var remote = UndoTabService.getTabOpetarionTargetsBy(data.remote);
  2334.         if (!remote.window)
  2335.             return aEvent.preventDefault();
  2336.  
  2337.         if (remote.tabs.length == data.remote.tabs.length) {
  2338.             remote.window['piro.sakura.ne.jp'].stopRendering.start();
  2339.             remote.window.MultipleTabService.getTabsArray(remote.browser)
  2340.                 .some(function(aTab) {
  2341.                     if (remote.tabs.indexOf(aTab) > -1)
  2342.                         return false;
  2343.                     // remote.window.MultipleTabService.irrevocableRemoveTab(aTab, remote.browser);
  2344.                     remote.browser.removeTab(aTab);
  2345.                     return true;
  2346.                 });
  2347.             this.moveTabsByIndex(
  2348.                 remote.browser,
  2349.                 remote.tabs.map(function(aTab, aIndex) {
  2350.                     return aTab._tPos;
  2351.                 }),
  2352.                 data.remote.positions
  2353.             );
  2354.             remote.window['piro.sakura.ne.jp'].stopRendering.start();
  2355.         }
  2356.  
  2357.         remote.window.setTimeout(function() {
  2358.             remote.window.UndoTabService.addEntry(remote.window, data.remote.entry);
  2359.             remote.window.UndoTabService.fakeRedo(remote.window, data.remote.entry);
  2360.         }, 250);
  2361.     },
  2362.  
  2363.     splitWindowFrom : function MTS_splitWindowFrom(aTabs) // old name, for backward compatibility 
  2364.     {
  2365.         return this.splitWindowFromTabs(aTabs);
  2366.     },
  2367.   
  2368.     importTabsTo : function MTS_importTabsTo() 
  2369.     {
  2370.         var aTabs = [],
  2371.             aTabBrowser,
  2372.             aClone;
  2373.         Array.slice(arguments).forEach(function(aArg) {
  2374.             if (typeof aArg == 'boolean') {
  2375.                 aClone = aArg;
  2376.             }
  2377.             else if (!aArg) {
  2378.                 return;
  2379.             }
  2380.             else if (aArg instanceof Components.interfaces.nsIDOMNode) {
  2381.                 if (aArg.localName == 'tabbrowser')
  2382.                     aTabBrowser = aArg;
  2383.                 else if (aArg.localName == 'tab')
  2384.                     aTabs.push(aArg);
  2385.             }
  2386.             else if (typeof aArg == 'object') {
  2387.                 aTabs = aTabs.concat(aArg);
  2388.             }
  2389.         });
  2390.  
  2391.         var importedTabs = [];
  2392.         if (!aTabs.length)
  2393.             return importedTabs;
  2394.  
  2395.         aTabs.forEach(this.ensureLoaded, this);
  2396.  
  2397.         this.duplicatingTabs = true;
  2398.  
  2399.         var targetBrowser = aTabBrowser || this.browser;
  2400.         var targetWindow  = targetBrowser.ownerDocument.defaultView;
  2401.         var sourceWindow  = aTabs[0].ownerDocument.defaultView;
  2402.         var sourceService = sourceWindow.MultipleTabService;
  2403.         var sourceBrowser = sourceService.getTabBrowserFromChild(aTabs[0]);
  2404.  
  2405.         targetWindow['piro.sakura.ne.jp'].stopRendering.stop();
  2406.         sourceWindow['piro.sakura.ne.jp'].stopRendering.stop();
  2407.  
  2408.         if (targetBrowser.__multipletab__canDoWindowMove && !aClone) {// move tabs, for Firefox 3.5 or later
  2409.             aTabs.forEach(function(aTab, aIndex) {
  2410.                 var newTab = targetBrowser.addTab();
  2411.                 importedTabs.push(newTab);
  2412.                 newTab.linkedBrowser.stop();
  2413.                 newTab.linkedBrowser.docShell;
  2414.                 targetBrowser.swapBrowsersAndCloseOther(newTab, aTab);
  2415.                 targetBrowser.setTabTitle(newTab);
  2416.                 this._duplicatedTabPostProcesses.forEach(function(aProcess) {
  2417.                     aProcess(newTab, newTab._tPos);
  2418.                 });
  2419.             }, this);
  2420.         }
  2421.         else { // duplicate tabs, or move tabs for Firefox 3.0
  2422.             aTabs.forEach(function(aTab) {
  2423.                 var newTab = targetBrowser.duplicateTab(aTab);
  2424.                 importedTabs.push(newTab);
  2425.                 this._duplicatedTabPostProcesses.forEach(function(aProcess) {
  2426.                     aProcess(newTab, newTab._tPos);
  2427.                 });
  2428.                 if (!aClone) {
  2429.                     sourceService.irrevocableRemoveTab(aTab, sourceBrowser);
  2430.                 }
  2431.             }, this);
  2432.         }
  2433.  
  2434.         targetWindow['piro.sakura.ne.jp'].stopRendering.start();
  2435.         sourceWindow['piro.sakura.ne.jp'].stopRendering.start();
  2436.  
  2437.         this.duplicatingTabs = false;
  2438.  
  2439.         return importedTabs;
  2440.     },
  2441.  
  2442.     registerClearTabValueKey : function MTS_registerClearTabValueKey(aKey) 
  2443.     {
  2444.         this._clearTabValueKeys.push(aKey);
  2445.     },
  2446.     _clearTabValueKeys : [],
  2447.  
  2448.     registerDuplicatedTabPostProcess : function MTS_registerDuplicatedTabPostProcess(aProcess) 
  2449.     {
  2450.         this._duplicatedTabPostProcesses.push(aProcess);
  2451.     },
  2452.     _duplicatedTabPostProcesses : [],
  2453.  
  2454.     copyURIsToClipboard : function MTS_copyURIsToClipboard(aTabs, aFormatType, aFormat) 
  2455.     {
  2456.         if (!aTabs) return;
  2457.         var string = this.formatURIsForClipboard(aTabs, aFormatType, aFormat);
  2458.         Components
  2459.             .classes['@mozilla.org/widget/clipboardhelper;1']
  2460.             .getService(Components.interfaces.nsIClipboardHelper)
  2461.             .copyString(string);
  2462.     },
  2463.     formatURIsForClipboard : function MTS_formatURIsForClipboard(aTabs, aFormatType, aFormat)
  2464.     {
  2465.         if (!aTabs) return '';
  2466.  
  2467.         if (aTabs instanceof Components.interfaces.nsIDOMNode) aTabs = [aTabs];
  2468.  
  2469.         aTabs.forEach(this.ensureLoaded, this);
  2470.  
  2471.         var format = aFormat || this.getClopboardFormatForType(aFormatType);
  2472.         if (!format) format = '%URL%';
  2473.  
  2474.         var now = new Date();
  2475.         var timeUTC = now.toUTCString();
  2476.         var timeLocal = now.toLocaleString();
  2477.  
  2478.         var stringToCopy = Array.slice(aTabs).map(function(aTab) {
  2479.                 let uri = aTab.linkedBrowser.currentURI.spec;
  2480.                 let title = aTab.linkedBrowser.contentDocument.title || aTab.getAttribute('label');
  2481.                 let escapedURI = uri
  2482.                                 .replace(/&/g, '&')
  2483.                                 .replace(/"/g, '"')
  2484.                                 .replace(/</g, '<')
  2485.                                 .replace(/>/g, '>');
  2486.                 let escapedTitle = title
  2487.                                 .replace(/&/g, '&')
  2488.                                 .replace(/"/g, '"')
  2489.                                 .replace(/</g, '<')
  2490.                                 .replace(/>/g, '>');
  2491.                 return format
  2492.                         .replace(/%(?:RLINK|RLINK_HTML(?:IFIED)?|SEL|SEL_HTML(?:IFIED)?)%/gi, '')
  2493.                         .replace(/%URL%/gi, uri)
  2494.                         .replace(/%(?:TITLE|TEXT)%/gi, title)
  2495.                         .replace(/%URL_HTML(?:IFIED)?%/gi, escapedURI)
  2496.                         .replace(/%TITLE_HTML(?:IFIED)?%/gi, escapedTitle)
  2497.                         .replace(/%UTC_TIME%/gi, timeUTC)
  2498.                         .replace(/%LOCAL_TIME%/gi, timeLocal)
  2499.                         .replace(/%EOL%/gi, this.lineFeed);
  2500.             }, this);
  2501.         if (stringToCopy.length > 1)
  2502.             stringToCopy.push('');
  2503.  
  2504.         return stringToCopy.join(this.lineFeed);
  2505.     },
  2506.     
  2507.     kFORMAT_TYPE_DEFAULT : 0, 
  2508.     kFORMAT_TYPE_MOZ_URL : 1,
  2509.     kFORMAT_TYPE_LINK    : 2,
  2510.  
  2511.     getClopboardFormatForType : function MTS_getClopboardFormatForType(aFormatType) 
  2512.     {
  2513.         if (aFormatType === void(0))
  2514.             aFormatType = this.getPref('extensions.multipletab.clipboard.formatType');
  2515.  
  2516.         switch (aFormatType)
  2517.         {
  2518.             default:
  2519.                 for (let i in this.formats)
  2520.                 {
  2521.                     if (this.formats[i].id == aFormatType)
  2522.                         return this.formats[i].format;
  2523.                 }
  2524.             case this.kFORMAT_TYPE_DEFAULT:
  2525.             case this.kFORMAT_TYPE_MOZ_URL:
  2526.             case this.kFORMAT_TYPE_LINK:
  2527.                 return this.getPref('extensions.multipletab.clipboard.format.'+aFormatType);
  2528.         }
  2529.     },
  2530.   
  2531.     // Tab Mix Plus commands 
  2532.     //   Tab Mix Plus:
  2533.     //     freeze, protect, lock
  2534.     //   Tab Utilities https://addons.mozilla.org/firefox/addon/59961
  2535.     //     freeze, protect
  2536.     //   Super Tab Mode https://addons.mozilla.org/firefox/addon/13288
  2537.     //     lock
  2538.     
  2539.     toggleTabsFreezed : function MTS_toggleTabsFreezed(aTabs, aNewState) 
  2540.     {
  2541.         if (aNewState === void(0))
  2542.             aNewState = !tabs.every(this._isTabFreezed);
  2543.  
  2544.         aTabs.forEach(function(aTab) {
  2545.             if (aNewState != this._isTabFreezed(aTab))
  2546.                 gBrowser.freezeTab(aTab);
  2547.         }, this);
  2548.     },
  2549.     _isTabFreezed : function MTS__isTabFreezed(aTab)
  2550.     {
  2551.         return aTab.hasAttribute('protected') && aTab.hasAttribute('locked');
  2552.     },
  2553.     get canFreezeTab()
  2554.     {
  2555.         return 'freezeTab' in gBrowser;
  2556.     },
  2557.  
  2558.     toggleTabsProtected : function MTS_toggleTabsProtected(aTabs, aNewState) 
  2559.     {
  2560.         if (aNewState === void(0))
  2561.             aNewState = !tabs.every(this._isTabProtected);
  2562.  
  2563.         aTabs.forEach(function(aTab) {
  2564.             if (aNewState != this._isTabProtected(aTab))
  2565.                 gBrowser.protectTab(aTab);
  2566.         }, this);
  2567.     },
  2568.     _isTabProtected : function MTS__isTabProtected(aTab)
  2569.     {
  2570.         return aTab.hasAttribute('protected');
  2571.     },
  2572.     get canProtectTab()
  2573.     {
  2574.         return 'protectTab' in gBrowser;
  2575.     },
  2576.  
  2577.     toggleTabsLocked : function MTS_toggleTabsLocked(aTabs, aNewState) 
  2578.     {
  2579.         if (aNewState === void(0))
  2580.             aNewState = !tabs.every(this._isTabLocked);
  2581.  
  2582.         aTabs.forEach(function(aTab) {
  2583.             if (aNewState == this._isTabLocked(aTab)) return;
  2584.  
  2585.             // Tab Mix Plus, Tab Utilities
  2586.             if ('lockTab' in gBrowser)
  2587.                 gBrowser.lockTab(aTab);
  2588.  
  2589.             // Super Tab Mode
  2590.             if ('stmM' in window && 'togglePL' in stmM) {
  2591.                 if (aNewState)
  2592.                     aTab.setAttribute('isPageLocked', true);
  2593.                 else
  2594.                     aTab.removeAttribute('isPageLocked');
  2595.             }
  2596.         }, this);
  2597.     },
  2598.     _isTabLocked : function MTS__isTabLocked(aTab)
  2599.     {
  2600.         return (
  2601.             aTab.hasAttribute('locked') || // Tab Mix Plus, Tab Utilities
  2602.             aTab.hasAttribute('isPageLocked') // Super Tab Mode
  2603.         );
  2604.     },
  2605.     get canLockTab()
  2606.     {
  2607.         return (
  2608.             'lockTab' in gBrowser || // Tab Mix Plus, Tab Utilities
  2609.             ('stmM' in window && 'togglePL' in stmM) // Super Tab Mode
  2610.         );
  2611.     },
  2612.    
  2613. /* Move and Duplicate multiple tabs on Drag and Drop */ 
  2614.     
  2615.     moveBundledTabsOf : function MTS_moveBundledTabsOf(aMovedTab, aEvent) 
  2616.     {
  2617.         var b = this.getTabBrowserFromChild(aMovedTab);
  2618.         var oldPosition = aEvent.detail;
  2619.         var movedTabs = this.getBundledTabsOf(aMovedTab);
  2620.         if (movedTabs.length <= 1)
  2621.             return;
  2622.         this.rearrangeBundledTabsOf(aMovedTab, oldPosition, movedTabs);
  2623.         b.mTabDropIndicatorBar.collapsed = true; // hide anyway!
  2624.     },
  2625.  
  2626.     importBundledTabsOf : function MTS_importBundledTabsOf(aNewTab, aSourceTab) 
  2627.     {
  2628.         var targetBrowser = this.getTabBrowserFromChild(aNewTab);
  2629.  
  2630.         if (!targetBrowser.__multipletab__canDoWindowMove) {
  2631.             this.duplicateBundledTabsOf(aNewTab, aSourceTab, true);
  2632.             retrurn;
  2633.         }
  2634.  
  2635.         var targetWindow = aNewTab.ownerDocument.defaultView;
  2636.         var targetService = targetWindow.MultipleTabService;
  2637.  
  2638.         var info = {};
  2639.         var sourceTabs = targetService.getBundledTabsOf(aSourceTab, info);
  2640.         var sourceWindow = info.sourceWindow;
  2641.         if (sourceTabs.length <= 1)
  2642.             return;
  2643.  
  2644.         var shouldSelectAfter = this.getPref('extensions.multipletab.selectAfter.move');
  2645.  
  2646.         var operation = function(aCollectData) {
  2647.             var result = {};
  2648.  
  2649.             var sourceBaseIndex = sourceTabs.indexOf(aSourceTab);
  2650.  
  2651.             var otherSourceTabs = sourceTabs.slice(0);
  2652.             otherSourceTabs.splice(otherSourceTabs.indexOf(aSourceTab), 1);
  2653.  
  2654.             var sourceService = sourceWindow.MultipleTabService;
  2655.             var sourceBrowser = info.sourceBrowser;
  2656.  
  2657.             result.source = !aCollectData ? null :
  2658.                 {
  2659.                     window  : UndoTabService.getId(sourceWindow),
  2660.                     browser : UndoTabService.getId(sourceBrowser),
  2661.                     tabs    : sourceTabs.map(function(aTab) {
  2662.                         return UndoTabService.getId(aTab);
  2663.                     }),
  2664.                     positions : sourceTabs.map(function(aTab) {
  2665.                         return aTab._tPos;
  2666.                     })
  2667.                 };
  2668.  
  2669.             var isAllTabsMove = sourceService.getTabs(sourceBrowser).snapshotLength == otherSourceTabs.length;
  2670.  
  2671.             targetBrowser.movingSelectedTabs = true;
  2672.             targetService.clearSelection(targetBrowser);
  2673.             sourceService.clearSelection(sourceBrowser);
  2674.  
  2675.             targetWindow['piro.sakura.ne.jp'].stopRendering.stop();
  2676.             sourceWindow['piro.sakura.ne.jp'].stopRendering.stop();
  2677.  
  2678.             var importedTabs = targetService.importTabsTo(otherSourceTabs, targetBrowser);
  2679.             importedTabs.splice(sourceBaseIndex, 0, aNewTab);
  2680.             targetService.rearrangeBundledTabsOf(aNewTab, importedTabs);
  2681.             if (shouldSelectAfter)
  2682.                 importedTabs.map(function(aTab) {
  2683.                     targetService.setSelection(aTab, true);
  2684.                 });
  2685.  
  2686.             result.target = !aCollectData ? null :
  2687.                 {
  2688.                     window  : UndoTabService.getId(targetWindow),
  2689.                     browser : UndoTabService.getId(targetBrowser),
  2690.                     tabs    : importedTabs.map(function(aTab) {
  2691.                         return UndoTabService.getId(aTab);
  2692.                     }),
  2693.                     positions : importedTabs.map(function(aTab) {
  2694.                         return aTab._tPos;
  2695.                     })
  2696.                 };
  2697.  
  2698.             if (isAllTabsMove) {
  2699.                 targetService.closeOwner(sourceBrowser);
  2700.             }
  2701.             else {
  2702.                 sourceWindow['piro.sakura.ne.jp'].stopRendering.start();
  2703.             }
  2704.  
  2705.             targetService.setSelection(aNewTab, shouldSelectAfter);
  2706.             targetBrowser.movingSelectedTabs = false;
  2707.  
  2708.             targetWindow['piro.sakura.ne.jp'].stopRendering.start();
  2709.  
  2710.             return result;
  2711.         };
  2712.  
  2713.         if ('UndoTabService' in window && UndoTabService.isUndoable()) {
  2714.             let data = {
  2715.                     source : null,
  2716.                     target : null
  2717.                 };
  2718.             let targetEntry = {
  2719.                     name  : 'multipletab-importBundledTabs-target',
  2720.                     label : this.bundle.getFormattedString('undo_importBundledTabsOf_target_label', [sourceTabs.length]),
  2721.                     data  : data
  2722.                 };
  2723.             let sourceEntry = {
  2724.                     name  : 'multipletab-importBundledTabs-source',
  2725.                     label : this.bundle.getFormattedString('undo_importBundledTabsOf_source_label', [sourceTabs.length]),
  2726.                     data  : data
  2727.                 };
  2728.             UndoTabService.doOperation(
  2729.                 function() {
  2730.                     UndoTabService.doOperation(
  2731.                         function() {
  2732.                             var result = operation(true);
  2733.                             data.source = result.source;
  2734.                             data.target = result.target;
  2735.                         },
  2736.                         sourceWindow,
  2737.                         sourceEntry
  2738.                     );
  2739.                 },
  2740.                 targetWindow,
  2741.                 targetEntry
  2742.             );
  2743.         }
  2744.         else {
  2745.             operation();
  2746.         }
  2747.     },
  2748.     windowMoveBundledTabsOf : function MTS_windowMoveBundledTabsOf(aNewTab, aSourceTab) // old name, for backward compatibility
  2749.     {
  2750.         return this.importBundledTabsOf(aNewTab, aSourceTab);
  2751.     },
  2752.     
  2753.     closeOwner : function MTS_closeOwner(aTabOwner) 
  2754.     {
  2755.         var w = aTabOwner.ownerDocument.defaultView;
  2756.         if (!w) return;
  2757.         if ('SplitBrowser' in w) {
  2758.             if ('getSubBrowserFromChild' in w.SplitBrowser) {
  2759.                 var subbrowser = w.SplitBrowser.getSubBrowserFromChild(aTabOwner);
  2760.                 if (subbrowser) {
  2761.                     subbrowser.close();
  2762.                     return;
  2763.                 }
  2764.             }
  2765.             if (w.SplitBrowser.browsers.length) return;
  2766.         }
  2767.         w.close();
  2768.     },
  2769.   
  2770.     duplicateBundledTabsOf : function MTS_duplicateBundledTabsOf(aNewTab, aSourceTab, aMayBeMove) 
  2771.     {
  2772.         var sourceWindow = aSourceTab.ownerDocument.defaultView;
  2773.         var sourceService = sourceWindow.MultipleTabService;
  2774.  
  2775.         var targetWindow = aNewTab.ownerDocument.defaultView;
  2776.         var targetService = targetWindow.MultipleTabService;
  2777.  
  2778.         var info = {};
  2779.         var sourceTabs = sourceService.getBundledTabsOf(aSourceTab, info);
  2780.         if (sourceTabs.length <= 1)
  2781.             return;
  2782.  
  2783.         var sourceBrowser = info.sourceBrowser;
  2784.         var targetBrowser = targetService.getTabBrowserFromChild(aNewTab);
  2785.  
  2786.         var isMove = (aMayBeMove && sourceBrowser != targetBrowser);
  2787.         var isAllTabsMove = (
  2788.                 isMove &&
  2789.                 targetWindow != sourceWindow &&
  2790.                 sourceService.getTabs(sourceBrowser).snapshotLength == sourceTabs.length
  2791.             );
  2792.         var shouldSelectAfter = this.getPref(isMove ?
  2793.                 'extensions.multipletab.selectAfter.move' :
  2794.                 'extensions.multipletab.selectAfter.duplicate'
  2795.             );
  2796.  
  2797.         var operation = function(aCollectData) {
  2798.             var result = {};
  2799.  
  2800.             result.source = !aCollectData ? null :
  2801.                 {
  2802.                     window  : UndoTabService.getId(sourceWindow),
  2803.                     browser : UndoTabService.getId(sourceBrowser),
  2804.                     tabs    : sourceTabs.map(function(aTab) {
  2805.                         return UndoTabService.getId(aTab);
  2806.                     }),
  2807.                     positions : sourceTabs.map(function(aTab) {
  2808.                         return aTab._tPos;
  2809.                     })
  2810.                 };
  2811.  
  2812.             var sourceBaseIndex = sourceTabs.indexOf(aSourceTab);
  2813.             var otherTabs = sourceTabs.slice(0);
  2814.             otherTabs.splice(sourceBaseIndex, 1);
  2815.  
  2816.             sourceService.clearSelection(sourceBrowser);
  2817.             targetService.clearSelection(targetBrowser);
  2818.  
  2819.             var otherSourceTabs = sourceTabs.slice(0);
  2820.             otherSourceTabs.splice(otherSourceTabs.indexOf(aSourceTab), 1);
  2821.  
  2822.             targetBrowser.duplicatingSelectedTabs = true;
  2823.             targetBrowser.movingSelectedTabs = true;
  2824.  
  2825.             var duplicatedTabs = targetService.importTabsTo(otherTabs, targetBrowser, !isMove);
  2826.             duplicatedTabs.splice(sourceBaseIndex, 0, aNewTab);
  2827.             targetService.rearrangeBundledTabsOf(aNewTab, duplicatedTabs);
  2828.  
  2829.             if (shouldSelectAfter)
  2830.                 duplicatedTabs.forEach(function(aTab) {
  2831.                     targetService.setSelection(aTab, true);
  2832.                 });
  2833.  
  2834.             result.target = !aCollectData ? null :
  2835.                 {
  2836.                     window  : UndoTabService.getId(targetWindow),
  2837.                     browser : UndoTabService.getId(targetBrowser),
  2838.                     tabs    : duplicatedTabs.map(function(aTab) {
  2839.                         return UndoTabService.getId(aTab);
  2840.                     }),
  2841.                     positions : duplicatedTabs.map(function(aTab) {
  2842.                         return aTab._tPos;
  2843.                     })
  2844.                 };
  2845.  
  2846.             if (isAllTabsMove)
  2847.                 targetService.closeOwner(sourceBrowser);
  2848.  
  2849.             targetBrowser.movingSelectedTabs = false;
  2850.             targetBrowser.duplicatingSelectedTabs = false;
  2851.             targetBrowser.mTabDropIndicatorBar.collapsed = true; // hide anyway!
  2852.  
  2853.             return result;
  2854.         };
  2855.  
  2856.         if ('UndoTabService' in window && UndoTabService.isUndoable()) {
  2857.             let data = {
  2858.                     source : null,
  2859.                     target : null
  2860.                 };
  2861.             let targetEntry = {
  2862.                 name  : isMove ?
  2863.                             'multipletab-importBundledTabs-source' :
  2864.                             'multipletab-duplicateBundledTabs-target',
  2865.                 label : this.bundle.getFormattedString(isMove ?
  2866.                             'undo_importBundledTabsOf_target_label' :
  2867.                             'undo_duplicateTabs_label',
  2868.                             [sourceTabs.length]
  2869.                         ),
  2870.                 data  : data
  2871.             };
  2872.             let sourceEntry = {
  2873.                 name  : isMove ?
  2874.                             'multipletab-importBundledTabs-source' :
  2875.                             'multipletab-duplicateBundledTabs-target',
  2876.                 label : this.bundle.getFormattedString(isMove ?
  2877.                             'undo_duplicateBundledTabsOf_source_label' :
  2878.                             'undo_duplicateTabs_label',
  2879.                             [sourceTabs.length]
  2880.                         ),
  2881.                 data  : data
  2882.             };
  2883.             if (sourceWindow == targetWindow) {
  2884.                 UndoTabService.doOperation(
  2885.                     function() {
  2886.                         var result = operation(true);
  2887.                         data.source = result.source;
  2888.                         data.target = result.target;
  2889.                     },
  2890.                     targetWindow,
  2891.                     targetEntry
  2892.                 );
  2893.             }
  2894.             else {
  2895.                 UndoTabService.doOperation(
  2896.                     function() {
  2897.                         UndoTabService.doOperation(
  2898.                             function() {
  2899.                                 var result = operation(true);
  2900.                                 data.source = result.source;
  2901.                                 data.target = result.target;
  2902.                             },
  2903.                             targetWindow,
  2904.                             targetEntry
  2905.                         );
  2906.                         return (isMove && !isAllTabsMove) ? true : false ;
  2907.                     },
  2908.                     sourceWindow,
  2909.                     sourceEntry
  2910.                 );
  2911.             }
  2912.         }
  2913.         else {
  2914.             operation();
  2915.         }
  2916.     },
  2917.  
  2918.     tearOffSelectedTabsFromRemote : function MTS_tearOffSelectedTabsFromRemote() 
  2919.     {
  2920.         var remoteTab = window.arguments[0];
  2921.         var info = {};
  2922.         var tabs = this.getBundledTabsOf(remoteTab, info);
  2923.         if (tabs.length > 1) {
  2924.             if (this.isDraggingAllTabs(remoteTab)) {
  2925.                 window.close();
  2926.             }
  2927.             else {
  2928.                 window.setTimeout(function() {
  2929.                     info.sourceWindow.MultipleTabService.splitWindowFromTabs(tabs, window);
  2930.                 }, 0);
  2931.             }
  2932.             return true;
  2933.         }
  2934.         return false;
  2935.     },
  2936.     
  2937.     isDraggingAllTabs : function MTS_isDraggingAllTabs(aTab) 
  2938.     {
  2939.         var info = {};
  2940.         var tabs = this.getBundledTabsOf(aTab, info);
  2941.         return tabs.length && tabs.length == info.sourceWindow.MultipleTabService.getTabs(info.sourceBrowser).snapshotLength;
  2942.     },
  2943.    
  2944. /* Tab Selection */ 
  2945.     
  2946.     hasSelection : function MTS_hasSelection(aTabBrowser) 
  2947.     {
  2948.         try {
  2949.             var xpathResult = document.evaluate(
  2950.                     'descendant::xul:tab[@'+this.kSELECTED+' = "true"]',
  2951.                     (aTabBrowser || this.browser).mTabContainer,
  2952.                     this.NSResolver, // document.createNSResolver(document.documentElement),
  2953.                     XPathResult.FIRST_ORDERED_NODE_TYPE,
  2954.                     null
  2955.                 );
  2956.             return xpathResult.singleNodeValue ? true : false ;
  2957.         }
  2958.         catch(e) {
  2959.         }
  2960.         return false;
  2961.     },
  2962.  
  2963.     isSelected : function MTS_isSelected(aTab) 
  2964.     {
  2965.         return aTab.getAttribute(this.kSELECTED) == 'true';
  2966.     },
  2967.  
  2968.     setSelection : function MTS_setSelection(aTab, aState) 
  2969.     {
  2970.         return this.setBooleanAttributeToTab(aTab, this.kSELECTED, aState, true);
  2971.     },
  2972.     
  2973.     setReadyToClose : function MTS_setReadyToClose(aTab, aState) 
  2974.     {
  2975.         return this.setBooleanAttributeToTab(aTab, this.kREADY_TO_CLOSE, aState, false);
  2976.     },
  2977.  
  2978.     setBooleanAttributeToTab : function MTS_setBooleanAttributeToTab(aTab, aAttr, aState, aShouldSaveToSession) 
  2979.     {
  2980.         if (!aState) {
  2981.             aTab.removeAttribute(aAttr);
  2982.             if (aShouldSaveToSession)
  2983.                 this.deleteTabValue(aTab, aAttr);
  2984.         }
  2985.         else {
  2986.             aTab.setAttribute(aAttr, true);
  2987.             if (aShouldSaveToSession)
  2988.                 this.setTabValue(aTab, aAttr, 'true');
  2989.         }
  2990.         this.selectionModified = true;
  2991.  
  2992.         if (
  2993.             'TreeStyleTabService' in window &&
  2994.             'getDescendantTabs' in TreeStyleTabService &&
  2995.             ('isCollapsed' in TreeStyleTabService ?
  2996.                 TreeStyleTabService.isSubtreeCollapsed(aTab) :
  2997.                 aTab.getAttribute(TreeStyleTabService.kSUBTREE_COLLAPSED) == 'true'
  2998.             )
  2999.             ) {
  3000.             var tabs = TreeStyleTabService.getDescendantTabs(aTab);
  3001.             for (var i = 0, maxi = tabs.length; i < maxi; i++)
  3002.             {
  3003.                 this.setBooleanAttributeToTab(tabs[i], aAttr, aState, aShouldSaveToSession);
  3004.             }
  3005.         }
  3006.  
  3007.         return aState;
  3008.     },
  3009.  
  3010.     setTabValue : function MTS_setTabValue(aTab, aKey, aValue) 
  3011.     {
  3012.         if (!aValue) return this.deleteTabValue(aTab, aKey);
  3013.  
  3014.         try {
  3015.             this.checkCachedSessionDataExpiration(aTab);
  3016.             this.SessionStore.setTabValue(aTab, aKey, aValue);
  3017.         }
  3018.         catch(e) {
  3019.         }
  3020.  
  3021.         return aValue;
  3022.     },
  3023.  
  3024.     deleteTabValue : function MTS_deleteTabValue(aTab, aKey) 
  3025.     {
  3026.         try {
  3027.             this.checkCachedSessionDataExpiration(aTab);
  3028.             this.SessionStore.setTabValue(aTab, aKey, '');
  3029.             this.SessionStore.deleteTabValue(aTab, aKey);
  3030.         }
  3031.         catch(e) {
  3032.         }
  3033.     },
  3034.  
  3035.     // workaround for http://piro.sakura.ne.jp/latest/blosxom/mozilla/extension/treestyletab/2009-09-29_debug.htm
  3036.     checkCachedSessionDataExpiration : function MTS_checkCachedSessionDataExpiration(aTab) 
  3037.     {
  3038.         var data = aTab.linkedBrowser.__SS_data || // Firefox 3.6-
  3039.                     aTab.linkedBrowser.parentNode.__SS_data; // -Firefox 3.5
  3040.         if (data &&
  3041.             data._tabStillLoading &&
  3042.             aTab.getAttribute('busy') != 'true')
  3043.             data._tabStillLoading = false;
  3044.     },
  3045.   
  3046.     toggleSelection : function MTS_toggleSelection(aTab) 
  3047.     {
  3048.         return this.toggleBooleanAttributeToTab(aTab, this.kSELECTED, true);
  3049.     },
  3050.     
  3051.     toggleReadyToClose : function MTS_toggleReadyToClose(aTab) 
  3052.     {
  3053.         return this.toggleBooleanAttributeToTab(aTab, this.kREADY_TO_CLOSE, false);
  3054.     },
  3055.  
  3056.     toggleBooleanAttributeToTab : function MTS_toggleBooleanAttributeToTab(aTab, aAttr, aShouldSaveToSession) 
  3057.     {
  3058.         return this.setBooleanAttributeToTab(aTab, aAttr, aTab.getAttribute(aAttr) != 'true', aShouldSaveToSession);
  3059.     },
  3060.   
  3061.     clearSelection : function MTS_clearSelection(aTabBrowser) 
  3062.     {
  3063.         this.clearSelectionSub(this.getSelectedTabs(aTabBrowser), this.kSELECTED);
  3064.         this.clearSelectionSub(this.getReadyToCloseTabs(aTabBrowser), this.kREADY_TO_CLOSE);
  3065.         this.selectionModified = false;
  3066.     },
  3067.     clearSelectionSub : function MTS_clearSelectionSub(aTabs, aAttr)
  3068.     {
  3069.         if (!aTabs || !aTabs.length) return;
  3070.  
  3071.         for (var i = aTabs.length-1; i > -1; i--)
  3072.         {
  3073.             aTabs[i].removeAttribute(aAttr);
  3074.             try {
  3075.                 this.SessionStore.setTabValue(aTabs[i], aAttr, '');
  3076.                 this.SessionStore.deleteTabValue(aTabs[i], aAttr);
  3077.             }
  3078.             catch(e) {
  3079.             }
  3080.         }
  3081.     },
  3082.     selectionModified : false,
  3083.   
  3084. /* Pref Listener */ 
  3085.     
  3086.     domain : 'extensions.multipletab', 
  3087.  
  3088.     observe : function MTS_observe(aSubject, aTopic, aPrefName) 
  3089.     {
  3090.         if (aTopic != 'nsPref:changed') return;
  3091.  
  3092.         var value = this.getPref(aPrefName);
  3093.         switch (aPrefName)
  3094.         {
  3095.             case 'extensions.multipletab.tabdrag.mode':
  3096.                 this.tabDragMode = value;
  3097.                 break;
  3098.  
  3099.             case 'extensions.multipletab.tabclick.accel.mode':
  3100.                 this.tabAccelClickMode = value;
  3101.                 break;
  3102.  
  3103.             case 'extensions.multipletab.tabclick.shift.mode':
  3104.                 this.tabShiftClickMode = value;
  3105.                 break;
  3106.  
  3107.             case 'extensions.multipletab.selectionStyle':
  3108.                 if (value == 'auto') {
  3109.                     value = ('tabColors' in window || 'CHROMATABS' in window) ? 'border' :
  3110.                             'color' ;
  3111.                 }
  3112.                 document.documentElement.setAttribute(this.kSELECTION_STYLE, value);
  3113.                 break;
  3114.  
  3115.             case 'extensions.multipletab.clipboard.linefeed':
  3116.                 this.lineFeed = value;
  3117.                 break;
  3118.  
  3119.             case 'extensions.multipletab.clipboard.formats':
  3120.                 this.formats = [];
  3121.                 this.formatsTimeStamp = Date.now();
  3122.                 value.split('|').forEach(function(aPart, aIndex) {
  3123.                     try {
  3124.                         let format, label;
  3125.                         [format, label] = aPart.split('/').map(decodeURIComponent);
  3126.                         if (!format) return;
  3127.                         if (!label) label = format;
  3128.                         this.formats.push({
  3129.                             id     : aIndex + this.kCUSTOM_TYPE_OFFSET,
  3130.                             label  : label,
  3131.                             format : format
  3132.                         });
  3133.                     }
  3134.                     catch(e) {
  3135.                     }
  3136.                 }, this);
  3137.                 break;
  3138.  
  3139.             default:
  3140.                 break;
  3141.         }
  3142.     }
  3143.   
  3144. }; 
  3145. MultipleTabService.__proto__ = window['piro.sakura.ne.jp'].prefs;
  3146.  
  3147. window.addEventListener('load', MultipleTabService, false);
  3148. window.addEventListener('DOMContentLoaded', MultipleTabService, false);
  3149.   
  3150.